18
18
import json
19
19
import os
20
20
import pkgutil
21
+ import pkg_resources
21
22
import re
22
23
import shutil
23
24
import sys
@@ -78,36 +79,7 @@ def pip_main(argv):
78
79
argv = ["--disable-pip-version-check" , "--cert" , cert_path ] + argv
79
80
return pip .main (argv )
80
81
81
-
82
- # TODO(mattmoor): We can't easily depend on other libraries when
83
- # being invoked as a raw .py file. Once bundled, we should be able
84
- # to remove this fallback on a stub implementation of Wheel.
85
- try :
86
- from rules_python .whl import Wheel
87
- except :
88
- class Wheel (object ):
89
-
90
- def __init__ (self , path ):
91
- self ._path = path
92
-
93
- def basename (self ):
94
- return os .path .basename (self ._path )
95
-
96
- def distribution (self ):
97
- # See https://www.python.org/dev/peps/pep-0427/#file-name-convention
98
- parts = self .basename ().split ('-' )
99
- return parts [0 ]
100
-
101
- def version (self ):
102
- # See https://www.python.org/dev/peps/pep-0427/#file-name-convention
103
- parts = self .basename ().split ('-' )
104
- return parts [1 ]
105
-
106
- def repository_name (self ):
107
- # Returns the canonical name of the Bazel repository for this package.
108
- canonical = 'pypi__{}_{}' .format (self .distribution (), self .version ())
109
- # Escape any illegal characters with underscore.
110
- return re .sub ('[-.]' , '_' , canonical )
82
+ from rules_python .whl import Wheel
111
83
112
84
parser = argparse .ArgumentParser (
113
85
description = 'Import Python dependencies into Bazel.' )
@@ -124,6 +96,59 @@ def repository_name(self):
124
96
parser .add_argument ('--directory' , action = 'store' ,
125
97
help = ('The directory into which to put .whl files.' ))
126
98
99
+ def determine_possible_extras (whls ):
100
+ """Determines the list of possible "extras" for each .whl
101
+
102
+ The possibility of an extra is determined by looking at its
103
+ additional requirements, and determinine whether they are
104
+ satisfied by the complete list of available wheels.
105
+
106
+ Args:
107
+ whls: a list of Wheel objects
108
+
109
+ Returns:
110
+ a dict that is keyed by the Wheel objects in whls, and whose
111
+ values are lists of possible extras.
112
+ """
113
+ whl_map = {
114
+ whl .distribution (): whl
115
+ for whl in whls
116
+ }
117
+
118
+ # TODO(mattmoor): Consider memoizing if this recursion ever becomes
119
+ # expensive enough to warrant it.
120
+ def is_possible (distro , extra ):
121
+ distro = distro .replace ("-" , "_" )
122
+ # If we don't have the .whl at all, then this isn't possible.
123
+ if distro not in whl_map :
124
+ return False
125
+ whl = whl_map [distro ]
126
+ # If we have the .whl, and we don't need anything extra then
127
+ # we can satisfy this dependency.
128
+ if not extra :
129
+ return True
130
+ # If we do need something extra, then check the extra's
131
+ # dependencies to make sure they are fully satisfied.
132
+ for extra_dep in whl .dependencies (extra = extra ):
133
+ req = pkg_resources .Requirement .parse (extra_dep )
134
+ # Check that the dep and any extras are all possible.
135
+ if not is_possible (req .project_name , None ):
136
+ return False
137
+ for e in req .extras :
138
+ if not is_possible (req .project_name , e ):
139
+ return False
140
+ # If all of the dependencies of the extra are satisfiable then
141
+ # it is possible to construct this dependency.
142
+ return True
143
+
144
+ return {
145
+ whl : [
146
+ extra
147
+ for extra in whl .extras ()
148
+ if is_possible (whl .distribution (), extra )
149
+ ]
150
+ for whl in whls
151
+ }
127
152
128
153
def main ():
129
154
args = parser .parse_args ()
@@ -140,6 +165,9 @@ def list_whls():
140
165
if fname .endswith ('.whl' ):
141
166
yield os .path .join (root , fname )
142
167
168
+ whls = [Wheel (path ) for path in list_whls ()]
169
+ possible_extras = determine_possible_extras (whls )
170
+
143
171
def whl_library (wheel ):
144
172
# Indentation here matters. whl_library must be within the scope
145
173
# of the function below. We also avoid reimporting an existing WHL.
@@ -149,10 +177,25 @@ def whl_library(wheel):
149
177
name = "{repo_name}",
150
178
whl = "@{name}//:{path}",
151
179
requirements = "@{name}//:requirements.bzl",
180
+ extras = [{extras}]
152
181
)""" .format (name = args .name , repo_name = wheel .repository_name (),
153
- path = wheel .basename ())
154
-
155
- whls = [Wheel (path ) for path in list_whls ()]
182
+ path = wheel .basename (),
183
+ extras = ',' .join ([
184
+ '"%s"' % extra
185
+ for extra in possible_extras .get (wheel , [])
186
+ ]))
187
+
188
+ whl_targets = ',' .join ([
189
+ ',' .join ([
190
+ '"%s": "@%s//:pkg"' % (whl .distribution ().lower (), whl .repository_name ())
191
+ ] + [
192
+ # For every extra that is possible from this requirements.txt
193
+ '"%s[%s]": "@%s//:%s"' % (whl .distribution ().lower (), extra .lower (),
194
+ whl .repository_name (), extra )
195
+ for extra in possible_extras .get (whl , [])
196
+ ])
197
+ for whl in whls
198
+ ])
156
199
157
200
with open (args .output , 'w' ) as f :
158
201
f .write ("""\
@@ -178,10 +221,7 @@ def requirement(name):
178
221
return _requirements[name_key]
179
222
""" .format (input = args .input ,
180
223
whl_libraries = '\n ' .join (map (whl_library , whls )) if whls else "pass" ,
181
- mappings = ',' .join ([
182
- '"%s": "@%s//:pkg"' % (wheel .distribution ().lower (), wheel .repository_name ())
183
- for wheel in whls
184
- ])))
224
+ mappings = whl_targets ))
185
225
186
226
if __name__ == '__main__' :
187
227
main ()
0 commit comments