17
17
import json
18
18
import os
19
19
import pkgutil
20
+ import pkg_resources
20
21
import re
21
22
import sys
22
23
import tempfile
23
24
import zipfile
24
25
25
- # PIP erroneously emits an error when bundled as a PAR file. We
26
- # disable the version check to silence it.
27
- try :
28
- # Make sure we're using a suitable version of pip as a library.
29
- # Fallback on using it as a CLI.
30
- from pip ._vendor import requests
31
-
32
- from pip import main as _pip_main
33
- def pip_main (argv ):
34
- # Extract the certificates from the PAR following the example of get-pip.py
35
- # https://github.com/pypa/get-pip/blob/430ba37776ae2ad89/template.py#L164-L168
36
- cert_path = os .path .join (tempfile .mkdtemp (), "cacert.pem" )
37
- with open (cert_path , "wb" ) as cert :
38
- cert .write (pkgutil .get_data ("pip._vendor.requests" , "cacert.pem" ))
39
- argv = ["--disable-pip-version-check" , "--cert" , cert_path ] + argv
40
- return _pip_main (argv )
41
-
42
- except :
43
- import subprocess
44
-
45
- def pip_main (argv ):
46
- return subprocess .call (['pip' ] + argv )
47
-
48
- # TODO(mattmoor): We can't easily depend on other libraries when
49
- # being invoked as a raw .py file. Once bundled, we should be able
50
- # to remove this fallback on a stub implementation of Wheel.
51
- try :
52
- from rules_python .whl import Wheel
53
- except :
54
- class Wheel (object ):
55
-
56
- def __init__ (self , path ):
57
- self ._path = path
58
-
59
- def basename (self ):
60
- return os .path .basename (self ._path )
61
-
62
- def distribution (self ):
63
- # See https://www.python.org/dev/peps/pep-0427/#file-name-convention
64
- parts = self .basename ().split ('-' )
65
- return parts [0 ]
66
-
67
- def version (self ):
68
- # See https://www.python.org/dev/peps/pep-0427/#file-name-convention
69
- parts = self .basename ().split ('-' )
70
- return parts [1 ]
71
-
72
- def repository_name (self ):
73
- # Returns the canonical name of the Bazel repository for this package.
74
- canonical = 'pypi__{}_{}' .format (self .distribution (), self .version ())
75
- # Escape any illegal characters with underscore.
76
- return re .sub ('[-.]' , '_' , canonical )
26
+ # Make sure we're using a suitable version of pip as a library.
27
+ # Fallback on using it as a CLI.
28
+ from pip ._vendor import requests
29
+
30
+ from pip import main as _pip_main
31
+ def pip_main (argv ):
32
+ # Extract the certificates from the PAR following the example of get-pip.py
33
+ # https://github.com/pypa/get-pip/blob/430ba37776ae2ad89/template.py#L164-L168
34
+ cert_path = os .path .join (tempfile .mkdtemp (), "cacert.pem" )
35
+ with open (cert_path , "wb" ) as cert :
36
+ cert .write (pkgutil .get_data ("pip._vendor.requests" , "cacert.pem" ))
37
+ # PIP erroneously emits an error when bundled as a PAR file. We
38
+ # disable the version check to silence it.
39
+ argv = ["--disable-pip-version-check" , "--cert" , cert_path ] + argv
40
+ return _pip_main (argv )
41
+
42
+ from rules_python .whl import Wheel
77
43
78
44
parser = argparse .ArgumentParser (
79
45
description = 'Import Python dependencies into Bazel.' )
@@ -90,6 +56,59 @@ def repository_name(self):
90
56
parser .add_argument ('--directory' , action = 'store' ,
91
57
help = ('The directory into which to put .whl files.' ))
92
58
59
+ def determine_possible_extras (whls ):
60
+ """Determines the list of possible "extras" for each .whl
61
+
62
+ The possibility of an extra is determined by looking at its
63
+ additional requirements, and determinine whether they are
64
+ satisfied by the complete list of available wheels.
65
+
66
+ Args:
67
+ whls: a list of Wheel objects
68
+
69
+ Returns:
70
+ a dict that is keyed by the Wheel objects in whls, and whose
71
+ values are lists of possible extras.
72
+ """
73
+ whl_map = {
74
+ whl .distribution (): whl
75
+ for whl in whls
76
+ }
77
+
78
+ # TODO(mattmoor): Consider memoizing if this recursion ever becomes
79
+ # expensive enough to warrant it.
80
+ def is_possible (distro , extra ):
81
+ distro = distro .replace ("-" , "_" )
82
+ # If we don't have the .whl at all, then this isn't possible.
83
+ if distro not in whl_map :
84
+ return False
85
+ whl = whl_map [distro ]
86
+ # If we have the .whl, and we don't need anything extra then
87
+ # we can satisfy this dependency.
88
+ if not extra :
89
+ return True
90
+ # If we do need something extra, then check the extra's
91
+ # dependencies to make sure they are fully satisfied.
92
+ for extra_dep in whl .dependencies (extra = extra ):
93
+ req = pkg_resources .Requirement .parse (extra_dep )
94
+ # Check that the dep and any extras are all possible.
95
+ if not is_possible (req .project_name , None ):
96
+ return False
97
+ for e in req .extras :
98
+ if not is_possible (req .project_name , e ):
99
+ return False
100
+ # If all of the dependencies of the extra are satisfiable then
101
+ # it is possible to construct this dependency.
102
+ return True
103
+
104
+ return {
105
+ whl : [
106
+ extra
107
+ for extra in whl .extras ()
108
+ if is_possible (whl .distribution (), extra )
109
+ ]
110
+ for whl in whls
111
+ }
93
112
94
113
def main ():
95
114
args = parser .parse_args ()
@@ -106,6 +125,9 @@ def list_whls():
106
125
if fname .endswith ('.whl' ):
107
126
yield os .path .join (root , fname )
108
127
128
+ whls = [Wheel (path ) for path in list_whls ()]
129
+ possible_extras = determine_possible_extras (whls )
130
+
109
131
def whl_library (wheel ):
110
132
# Indentation here matters. whl_library must be within the scope
111
133
# of the function below. We also avoid reimporting an existing WHL.
@@ -115,10 +137,25 @@ def whl_library(wheel):
115
137
name = "{repo_name}",
116
138
whl = "@{name}//:{path}",
117
139
requirements = "@{name}//:requirements.bzl",
140
+ extras = [{extras}]
118
141
)""" .format (name = args .name , repo_name = wheel .repository_name (),
119
- path = wheel .basename ())
120
-
121
- whls = [Wheel (path ) for path in list_whls ()]
142
+ path = wheel .basename (),
143
+ extras = ',' .join ([
144
+ '"%s"' % extra
145
+ for extra in possible_extras .get (wheel , [])
146
+ ]))
147
+
148
+ whl_targets = ',' .join ([
149
+ ',' .join ([
150
+ '"%s": "@%s//:pkg"' % (whl .distribution ().lower (), whl .repository_name ())
151
+ ] + [
152
+ # For every extra that is possible from this requirements.txt
153
+ '"%s[%s]": "@%s//:%s"' % (whl .distribution ().lower (), extra .lower (),
154
+ whl .repository_name (), extra )
155
+ for extra in possible_extras .get (whl , [])
156
+ ])
157
+ for whl in whls
158
+ ])
122
159
123
160
with open (args .output , 'w' ) as f :
124
161
f .write ("""\
@@ -142,10 +179,7 @@ def requirement(name):
142
179
return _requirements[name]
143
180
""" .format (input = args .input ,
144
181
whl_libraries = '\n ' .join (map (whl_library , whls )),
145
- mappings = ',' .join ([
146
- '"%s": "@%s//:pkg"' % (wheel .distribution ().lower (), wheel .repository_name ())
147
- for wheel in whls
148
- ])))
182
+ mappings = whl_targets ))
149
183
150
184
if __name__ == '__main__' :
151
185
main ()
0 commit comments