forked from bazel-contrib/rules_python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpiptool.py
More file actions
154 lines (125 loc) · 4.91 KB
/
Copy pathpiptool.py
File metadata and controls
154 lines (125 loc) · 4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The piptool module imports pip requirements into Bazel rules."""
import argparse
import json
import os
import pkgutil
import re
import sys
import tempfile
import zipfile
# TODO(mattmoor): When this tool is invoked bundled as a PAR file,
# but not as a py_binary, we get a warning that indicates the system
# installed version of PIP is being picked up instead of our bundled
# version, which should be 9.0.1, e.g.
# You are using pip version 1.5.4, however version 9.0.1 is available.
# You should consider upgrading via the 'pip install --upgrade pip' command.
try:
# Make sure we're using a suitable version of pip as a library.
# Fallback on using it as a CLI.
from pip._vendor import requests
from pip import main as _pip_main
def pip_main(argv):
# Extract the certificates from the PAR following the example of get-pip.py
# https://github.com/pypa/get-pip/blob/430ba37776ae2ad89/template.py#L164-L168
cert_path = os.path.join(tempfile.mkdtemp(), "cacert.pem")
with open(cert_path, "wb") as cert:
cert.write(pkgutil.get_data("pip._vendor.requests", "cacert.pem"))
return _pip_main(argv + ["--cert", cert_path])
except:
import subprocess
def pip_main(argv):
return subprocess.call(['pip'] + argv)
# TODO(mattmoor): We can't easily depend on other libraries when
# being invoked as a raw .py file. Once bundled, we should be able
# to remove this fallback on a stub implementation of Wheel.
try:
from rules_python.whl import Wheel
except:
class Wheel(object):
def __init__(self, path):
self._path = path
def basename(self):
return os.path.basename(self._path)
def distribution(self):
# See https://www.python.org/dev/peps/pep-0427/#file-name-convention
parts = self.basename().split('-')
return parts[0]
def version(self):
# See https://www.python.org/dev/peps/pep-0427/#file-name-convention
parts = self.basename().split('-')
return parts[1]
def repository_name(self):
# Returns the canonical name of the Bazel repository for this package.
canonical = 'pypi__{}_{}'.format(self.distribution(), self.version())
# Escape any illegal characters with underscore.
return re.sub('[-.]', '_', canonical)
parser = argparse.ArgumentParser(
description='Import Python dependencies into Bazel.')
parser.add_argument('--name', action='store',
help=('The namespace of the import.'))
parser.add_argument('--input', action='store',
help=('The requirements.txt file to import.'))
parser.add_argument('--output', action='store',
help=('The requirements.bzl file to export.'))
parser.add_argument('--directory', action='store',
help=('The directory into which to put .whl files.'))
def main():
args = parser.parse_args()
# https://github.com/pypa/pip/blob/9.0.1/pip/__init__.py#L209
if pip_main(["wheel", "-w", args.directory, "-r", args.input]):
sys.exit(1)
# Enumerate the .whl files we downloaded.
def list_whls():
dir = args.directory + '/'
for root, unused_dirnames, filenames in os.walk(dir):
for fname in filenames:
if fname.endswith('.whl'):
yield os.path.join(root, fname)
def whl_library(wheel):
# Indentation here matters. whl_library must be within the scope
# of the function below. We also avoid reimporting an existing WHL.
return """
if "{repo_name}" not in native.existing_rules():
whl_library(
name = "{repo_name}",
whl = "@{name}//:{path}",
requirements = "@{name}//:requirements.bzl",
)""".format(name=args.name, repo_name=wheel.repository_name(),
path=wheel.basename())
whls = [Wheel(path) for path in list_whls()]
with open(args.output, 'w') as f:
f.write("""\
# Install pip requirements.
#
# Generated from {input}
load("@io_bazel_rules_python//python:whl.bzl", "whl_library")
def pip_install():
{whl_libraries}
_packages = {{
{mappings}
}}
all_packages = _packages.values()
def package(name):
name = name.replace("-", "_")
return _packages[name]
""".format(input=args.input,
whl_libraries='\n'.join(map(whl_library, whls)),
mappings=','.join([
'"%s": "@%s//:pkg"' % (wheel.distribution(), wheel.repository_name())
for wheel in whls
])))
if __name__ == '__main__':
main()