9
9
contains a set of useful utilities for including npm packages
10
10
within a Python package.
11
11
"""
12
+ from collections import defaultdict
12
13
from os .path import join as pjoin
13
14
import io
14
15
import os
20
21
import sys
21
22
22
23
23
- from setuptools import Command , setup
24
- from setuptools .command .build_py import build_py
25
- from setuptools .command .sdist import sdist
24
+ # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
25
+ # update it when the contents of directories change.
26
+ if os .path .exists ('MANIFEST' ): os .remove ('MANIFEST' )
27
+
28
+
29
+ from distutils .cmd import Command
30
+ from distutils .command .build_py import build_py
31
+ from distutils .command .sdist import sdist
32
+ from distutils import log
33
+
26
34
from setuptools .command .develop import develop
27
35
from setuptools .command .bdist_egg import bdist_egg
28
- from distutils import log
29
36
30
37
try :
31
38
from wheel .bdist_wheel import bdist_wheel
@@ -117,6 +124,17 @@ def update_package_data(distribution):
117
124
build_py .finalize_options ()
118
125
119
126
127
+ class bdist_egg_disabled (bdist_egg ):
128
+ """Disabled version of bdist_egg
129
+
130
+ Prevents setup.py install performing setuptools' default easy_install,
131
+ which it should never ever do.
132
+ """
133
+ def run (self ):
134
+ sys .exit ("Aborting implicit building of eggs. Use `pip install .` "
135
+ " to install from source." )
136
+
137
+
120
138
def create_cmdclass (prerelease_cmd = None , package_data_spec = None ,
121
139
data_files_spec = None ):
122
140
"""Create a command class with the given optional prerelease class.
@@ -129,8 +147,9 @@ def create_cmdclass(prerelease_cmd=None, package_data_spec=None,
129
147
A dictionary whose keys are the dotted package names and
130
148
whose values are a list of glob patterns.
131
149
data_files_spec: list, optional
132
- A list of (path, patterns) tuples where the path is the
133
- `data_files` install path and the patterns are glob patterns.
150
+ A list of (path, dname, pattern) tuples where the path is the
151
+ `data_files` install path, dname is the source directory, and the
152
+ pattern is a glob pattern.
134
153
135
154
Notes
136
155
-----
@@ -142,28 +161,35 @@ def create_cmdclass(prerelease_cmd=None, package_data_spec=None,
142
161
name.
143
162
e.g. `dict(foo=['./bar/*', './baz/**'])`
144
163
145
- The data files glob patterns should be absolute paths or relative paths
146
- from the root directory of the repository.
147
- e.g. `('share/foo/bar', ['pkgname/bizz/*', 'pkgname/baz/**'])`
164
+ The data files directories should be absolute paths or relative paths
165
+ from the root directory of the repository. Data files are specified
166
+ differently from `package_data` because we need a separate path entry
167
+ for each nested folder in `data_files`, and this makes it easier to
168
+ parse.
169
+ e.g. `('share/foo/bar', 'pkgname/bizz, '*')`
148
170
"""
149
- egg = bdist_egg if 'bdist_egg' in sys .argv else bdist_egg_disabled
150
171
wrapped = [prerelease_cmd ] if prerelease_cmd else []
151
172
if package_data_spec or data_files_spec :
152
173
wrapped .append ('handle_files' )
153
174
wrapper = functools .partial (_wrap_command , wrapped )
154
175
handle_files = _get_file_handler (package_data_spec , data_files_spec )
155
176
177
+ if 'bdist_egg' in sys .argv :
178
+ egg = wrapper (bdist_egg , strict = True )
179
+ else :
180
+ egg = bdist_egg_disabled
181
+
156
182
cmdclass = dict (
157
183
build_py = wrapper (build_py , strict = is_repo ),
158
- sdist = wrapper (sdist , strict = True ),
159
184
bdist_egg = egg ,
160
- develop = wrapper (develop , strict = True ),
185
+ sdist = wrapper (sdist , strict = True ),
161
186
handle_files = handle_files ,
162
187
)
163
188
164
189
if bdist_wheel :
165
190
cmdclass ['bdist_wheel' ] = wrapper (bdist_wheel , strict = True )
166
191
192
+ cmdclass ['develop' ] = wrapper (develop , strict = True )
167
193
return cmdclass
168
194
169
195
@@ -221,6 +247,7 @@ def combine_commands(*commands):
221
247
"""Return a Command that combines several commands."""
222
248
223
249
class CombinedCommand (Command ):
250
+ user_options = []
224
251
225
252
def initialize_options (self ):
226
253
self .commands = []
@@ -448,23 +475,14 @@ def run(self):
448
475
449
476
update_packages (self )
450
477
451
- result = cls .run (self )
452
478
# update package data
453
479
update_package_data (self .distribution )
480
+
481
+ result = cls .run (self )
454
482
return result
455
483
return WrappedCommand
456
484
457
485
458
- class bdist_egg_disabled (bdist_egg ):
459
- """Disabled version of bdist_egg
460
- Prevents setup.py install performing setuptools' default easy_install,
461
- which it should never ever do.
462
- """
463
- def run (self ):
464
- sys .exit ("Aborting implicit building of eggs. Use `pip install .` " +
465
- " to install from source." )
466
-
467
-
468
486
def _get_file_handler (package_data_spec , data_files_spec ):
469
487
"""Get a package_data and data_files handler command.
470
488
"""
@@ -473,20 +491,58 @@ class FileHandler(BaseCommand):
473
491
def run (self ):
474
492
package_data = self .distribution .package_data
475
493
package_spec = package_data_spec or dict ()
476
- data_spec = data_files_spec or []
477
494
478
495
for (key , patterns ) in package_spec .items ():
479
496
package_data [key ] = _get_package_data (key , patterns )
480
497
481
- data_files = self .distribution .data_files or []
482
- for (path , patterns ) in data_spec :
483
- data_files .append ((path , _get_files (patterns )))
484
-
485
- self .distribution .data_files = data_files
498
+ self .distribution .data_files = _get_data_files (
499
+ data_files_spec , self .distribution .data_files
500
+ )
486
501
487
502
return FileHandler
488
503
489
504
505
+ def _get_data_files (data_specs , existing ):
506
+ """Expand data file specs into valid data files metadata.
507
+
508
+ Parameters
509
+ ----------
510
+ data_specs: list of tuples
511
+ See [createcmdclass] for description.
512
+ existing: list of tuples
513
+ The existing distrubution data_files metadata.
514
+
515
+ Returns
516
+ -------
517
+ A valid list of data_files items.
518
+ """
519
+ # Extract the existing data files into a staging object.
520
+ file_data = defaultdict (list )
521
+ for (path , files ) in existing or []:
522
+ file_data [path ] = files
523
+
524
+ # Extract the files and assign them to the proper data
525
+ # files path.
526
+ for (path , dname , pattern ) in data_specs or []:
527
+ dname = dname .replace (os .sep , '/' )
528
+ offset = len (dname ) + 1
529
+
530
+ files = _get_files (pjoin (dname , pattern ))
531
+ for fname in files :
532
+ # Normalize the path.
533
+ root = os .path .dirname (fname )
534
+ full_path = '/' .join ([path , root [offset :]])
535
+ if full_path .endswith ('/' ):
536
+ full_path = full_path [:- 1 ]
537
+ file_data [full_path ].append (fname )
538
+
539
+ # Construct the data files spec.
540
+ data_files = []
541
+ for (path , files ) in file_data .items ():
542
+ data_files .append ((path , files ))
543
+ return data_files
544
+
545
+
490
546
def _get_files (file_patterns , top = HERE ):
491
547
"""Expand file patterns to a list of paths.
492
548
@@ -621,7 +677,7 @@ def _translate_glob_part(pat):
621
677
res = []
622
678
while i < n :
623
679
c = pat [i ]
624
- i = i + 1
680
+ i = i + 1
625
681
if c == '*' :
626
682
# Match anything but path separators:
627
683
res .append ('[^%s]*' % SEPARATORS )
@@ -630,16 +686,16 @@ def _translate_glob_part(pat):
630
686
elif c == '[' :
631
687
j = i
632
688
if j < n and pat [j ] == '!' :
633
- j = j + 1
689
+ j = j + 1
634
690
if j < n and pat [j ] == ']' :
635
- j = j + 1
691
+ j = j + 1
636
692
while j < n and pat [j ] != ']' :
637
- j = j + 1
693
+ j = j + 1
638
694
if j >= n :
639
695
res .append ('\\ [' )
640
696
else :
641
697
stuff = pat [i :j ].replace ('\\ ' , '\\ \\ ' )
642
- i = j + 1
698
+ i = j + 1
643
699
if stuff [0 ] == '!' :
644
700
stuff = '^' + stuff [1 :]
645
701
elif stuff [0 ] == '^' :
0 commit comments