~anitanayak/charms/trusty/ibm-mq/devel

« back to all changes in this revision

Viewing changes to .tox/py35/lib/python3.5/site-packages/wheel/bdist_wheel.py

  • Committer: Anita Nayak
  • Date: 2016-10-24 07:11:28 UTC
  • Revision ID: anitanayak@in.ibm.com-20161024071128-oufsbvyx8x344p2j
checking in after fixing lint errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Create a wheel (.whl) distribution.
 
3
 
 
4
A wheel is a built archive format.
 
5
"""
 
6
 
 
7
import csv
 
8
import hashlib
 
9
import os
 
10
import subprocess
 
11
import warnings
 
12
import shutil
 
13
import json
 
14
import sys
 
15
import re
 
16
 
 
17
try:
 
18
    import sysconfig
 
19
except ImportError:  # pragma nocover
 
20
    # Python < 2.7
 
21
    import distutils.sysconfig as sysconfig
 
22
 
 
23
import pkg_resources
 
24
 
 
25
safe_name = pkg_resources.safe_name
 
26
safe_version = pkg_resources.safe_version
 
27
 
 
28
from shutil import rmtree
 
29
from email.generator import Generator
 
30
 
 
31
from distutils.core import Command
 
32
from distutils.sysconfig import get_python_version
 
33
 
 
34
from distutils import log as logger
 
35
 
 
36
from .pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag, get_platform
 
37
from .util import native, open_for_csv
 
38
from .archive import archive_wheelfile
 
39
from .pkginfo import read_pkg_info, write_pkg_info
 
40
from .metadata import pkginfo_to_dict
 
41
from . import pep425tags, metadata
 
42
from . import __version__ as wheel_version
 
43
 
 
44
PY_LIMITED_API_PATTERN = r'cp3\d'
 
45
 
 
46
def safer_name(name):
 
47
    return safe_name(name).replace('-', '_')
 
48
 
 
49
def safer_version(version):
 
50
    return safe_version(version).replace('-', '_')
 
51
 
 
52
class bdist_wheel(Command):
 
53
 
 
54
    description = 'create a wheel distribution'
 
55
 
 
56
    user_options = [('bdist-dir=', 'b',
 
57
                     "temporary directory for creating the distribution"),
 
58
                    ('plat-name=', 'p',
 
59
                     "platform name to embed in generated filenames "
 
60
                     "(default: %s)" % get_platform()),
 
61
                    ('keep-temp', 'k',
 
62
                     "keep the pseudo-installation tree around after " +
 
63
                     "creating the distribution archive"),
 
64
                    ('dist-dir=', 'd',
 
65
                     "directory to put final built distributions in"),
 
66
                    ('skip-build', None,
 
67
                     "skip rebuilding everything (for testing/debugging)"),
 
68
                    ('relative', None,
 
69
                     "build the archive using relative paths"
 
70
                     "(default: false)"),
 
71
                    ('owner=', 'u',
 
72
                     "Owner name used when creating a tar file"
 
73
                     " [default: current user]"),
 
74
                    ('group=', 'g',
 
75
                     "Group name used when creating a tar file"
 
76
                     " [default: current group]"),
 
77
                    ('universal', None,
 
78
                     "make a universal wheel"
 
79
                     " (default: false)"),
 
80
                    ('python-tag=', None,
 
81
                     "Python implementation compatibility tag"
 
82
                     " (default: py%s)" % get_impl_ver()[0]),
 
83
                    ('py-limited-api=', None,
 
84
                     "Python tag (cp32|cp33|cpNN) for abi3 wheel tag"
 
85
                     " (default: false)"),
 
86
                    ]
 
87
 
 
88
    boolean_options = ['keep-temp', 'skip-build', 'relative', 'universal']
 
89
 
 
90
    def initialize_options(self):
 
91
        self.bdist_dir = None
 
92
        self.data_dir = None
 
93
        self.plat_name = None
 
94
        self.plat_tag = None
 
95
        self.format = 'zip'
 
96
        self.keep_temp = False
 
97
        self.dist_dir = None
 
98
        self.distinfo_dir = None
 
99
        self.egginfo_dir = None
 
100
        self.root_is_pure = None
 
101
        self.skip_build = None
 
102
        self.relative = False
 
103
        self.owner = None
 
104
        self.group = None
 
105
        self.universal = False
 
106
        self.python_tag = 'py' + get_impl_ver()[0]
 
107
        self.py_limited_api = False
 
108
        self.plat_name_supplied = False
 
109
 
 
110
    def finalize_options(self):
 
111
        if self.bdist_dir is None:
 
112
            bdist_base = self.get_finalized_command('bdist').bdist_base
 
113
            self.bdist_dir = os.path.join(bdist_base, 'wheel')
 
114
 
 
115
        self.data_dir = self.wheel_dist_name + '.data'
 
116
        self.plat_name_supplied = self.plat_name is not None
 
117
 
 
118
        need_options = ('dist_dir', 'plat_name', 'skip_build')
 
119
 
 
120
        self.set_undefined_options('bdist',
 
121
                                   *zip(need_options, need_options))
 
122
 
 
123
        self.root_is_pure = not (self.distribution.has_ext_modules()
 
124
                                 or self.distribution.has_c_libraries())
 
125
 
 
126
        if self.py_limited_api and not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api):
 
127
            raise ValueError("py-limited-api must match '%s'" % PY_LIMITED_API_PATTERN)
 
128
 
 
129
        # Support legacy [wheel] section for setting universal
 
130
        wheel = self.distribution.get_option_dict('wheel')
 
131
        if 'universal' in wheel:
 
132
            # please don't define this in your global configs
 
133
            val = wheel['universal'][1].strip()
 
134
            if val.lower() in ('1', 'true', 'yes'):
 
135
                self.universal = True
 
136
 
 
137
    @property
 
138
    def wheel_dist_name(self):
 
139
        """Return distribution full name with - replaced with _"""
 
140
        return '-'.join((safer_name(self.distribution.get_name()),
 
141
                         safer_version(self.distribution.get_version())))
 
142
 
 
143
    def get_tag(self):
 
144
        # bdist sets self.plat_name if unset, we should only use it for purepy
 
145
        # wheels if the user supplied it.
 
146
        if self.plat_name_supplied:
 
147
            plat_name = self.plat_name
 
148
        elif self.root_is_pure:
 
149
            plat_name = 'any'
 
150
        else:
 
151
            plat_name = self.plat_name or get_platform()
 
152
            if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647:
 
153
                plat_name = 'linux_i686'
 
154
        plat_name = plat_name.replace('-', '_').replace('.', '_')
 
155
 
 
156
 
 
157
        if self.root_is_pure:
 
158
            if self.universal:
 
159
                impl = 'py2.py3'
 
160
            else:
 
161
                impl = self.python_tag
 
162
            tag = (impl, 'none', plat_name)
 
163
        else:
 
164
            impl_name = get_abbr_impl()
 
165
            impl_ver = get_impl_ver()
 
166
            impl = impl_name + impl_ver
 
167
            # We don't work on CPython 3.1, 3.0.
 
168
            if self.py_limited_api and (impl_name + impl_ver).startswith('cp3'):
 
169
                impl = self.py_limited_api
 
170
                abi_tag = 'abi3'
 
171
            else:
 
172
                abi_tag = str(get_abi_tag()).lower()
 
173
            tag = (impl, abi_tag, plat_name)
 
174
            supported_tags = pep425tags.get_supported(
 
175
                supplied_platform=plat_name if self.plat_name_supplied else None)
 
176
            # XXX switch to this alternate implementation for non-pure:
 
177
            if not self.py_limited_api:
 
178
                assert tag == supported_tags[0], "%s != %s" % (tag, supported_tags[0])
 
179
            assert tag in supported_tags, "would build wheel with unsupported tag %s" % tag
 
180
        return tag
 
181
 
 
182
    def get_archive_basename(self):
 
183
        """Return archive name without extension"""
 
184
 
 
185
        impl_tag, abi_tag, plat_tag = self.get_tag()
 
186
 
 
187
        archive_basename = "%s-%s-%s-%s" % (
 
188
            self.wheel_dist_name,
 
189
            impl_tag,
 
190
            abi_tag,
 
191
            plat_tag)
 
192
        return archive_basename
 
193
 
 
194
    def run(self):
 
195
        build_scripts = self.reinitialize_command('build_scripts')
 
196
        build_scripts.executable = 'python'
 
197
 
 
198
        if not self.skip_build:
 
199
            self.run_command('build')
 
200
 
 
201
        install = self.reinitialize_command('install',
 
202
                                            reinit_subcommands=True)
 
203
        install.root = self.bdist_dir
 
204
        install.compile = False
 
205
        install.skip_build = self.skip_build
 
206
        install.warn_dir = False
 
207
 
 
208
        # A wheel without setuptools scripts is more cross-platform.
 
209
        # Use the (undocumented) `no_ep` option to setuptools'
 
210
        # install_scripts command to avoid creating entry point scripts.
 
211
        install_scripts = self.reinitialize_command('install_scripts')
 
212
        install_scripts.no_ep = True
 
213
 
 
214
        # Use a custom scheme for the archive, because we have to decide
 
215
        # at installation time which scheme to use.
 
216
        for key in ('headers', 'scripts', 'data', 'purelib', 'platlib'):
 
217
            setattr(install,
 
218
                    'install_' + key,
 
219
                    os.path.join(self.data_dir, key))
 
220
 
 
221
        basedir_observed = ''
 
222
 
 
223
        if os.name == 'nt':
 
224
            # win32 barfs if any of these are ''; could be '.'?
 
225
            # (distutils.command.install:change_roots bug)
 
226
            basedir_observed = os.path.normpath(os.path.join(self.data_dir, '..'))
 
227
            self.install_libbase = self.install_lib = basedir_observed
 
228
 
 
229
        setattr(install,
 
230
                'install_purelib' if self.root_is_pure else 'install_platlib',
 
231
                basedir_observed)
 
232
 
 
233
        logger.info("installing to %s", self.bdist_dir)
 
234
 
 
235
        self.run_command('install')
 
236
 
 
237
        archive_basename = self.get_archive_basename()
 
238
 
 
239
        pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
 
240
        if not self.relative:
 
241
            archive_root = self.bdist_dir
 
242
        else:
 
243
            archive_root = os.path.join(
 
244
                self.bdist_dir,
 
245
                self._ensure_relative(install.install_base))
 
246
 
 
247
        self.set_undefined_options(
 
248
            'install_egg_info', ('target', 'egginfo_dir'))
 
249
        self.distinfo_dir = os.path.join(self.bdist_dir,
 
250
                                         '%s.dist-info' % self.wheel_dist_name)
 
251
        self.egg2dist(self.egginfo_dir,
 
252
                      self.distinfo_dir)
 
253
 
 
254
        self.write_wheelfile(self.distinfo_dir)
 
255
 
 
256
        self.write_record(self.bdist_dir, self.distinfo_dir)
 
257
 
 
258
        # Make the archive
 
259
        if not os.path.exists(self.dist_dir):
 
260
            os.makedirs(self.dist_dir)
 
261
        wheel_name = archive_wheelfile(pseudoinstall_root, archive_root)
 
262
 
 
263
        # Sign the archive
 
264
        if 'WHEEL_TOOL' in os.environ:
 
265
            subprocess.call([os.environ['WHEEL_TOOL'], 'sign', wheel_name])
 
266
 
 
267
        # Add to 'Distribution.dist_files' so that the "upload" command works
 
268
        getattr(self.distribution, 'dist_files', []).append(
 
269
            ('bdist_wheel', get_python_version(), wheel_name))
 
270
 
 
271
        if not self.keep_temp:
 
272
            if self.dry_run:
 
273
                logger.info('removing %s', self.bdist_dir)
 
274
            else:
 
275
                rmtree(self.bdist_dir)
 
276
 
 
277
    def write_wheelfile(self, wheelfile_base, generator='bdist_wheel (' + wheel_version + ')'):
 
278
        from email.message import Message
 
279
        msg = Message()
 
280
        msg['Wheel-Version'] = '1.0'  # of the spec
 
281
        msg['Generator'] = generator
 
282
        msg['Root-Is-Purelib'] = str(self.root_is_pure).lower()
 
283
 
 
284
        # Doesn't work for bdist_wininst
 
285
        impl_tag, abi_tag, plat_tag = self.get_tag()
 
286
        for impl in impl_tag.split('.'):
 
287
            for abi in abi_tag.split('.'):
 
288
                for plat in plat_tag.split('.'):
 
289
                    msg['Tag'] = '-'.join((impl, abi, plat))
 
290
 
 
291
        wheelfile_path = os.path.join(wheelfile_base, 'WHEEL')
 
292
        logger.info('creating %s', wheelfile_path)
 
293
        with open(wheelfile_path, 'w') as f:
 
294
            Generator(f, maxheaderlen=0).flatten(msg)
 
295
 
 
296
    def _ensure_relative(self, path):
 
297
        # copied from dir_util, deleted
 
298
        drive, path = os.path.splitdrive(path)
 
299
        if path[0:1] == os.sep:
 
300
            path = drive + path[1:]
 
301
        return path
 
302
 
 
303
    def _pkginfo_to_metadata(self, egg_info_path, pkginfo_path):
 
304
        return metadata.pkginfo_to_metadata(egg_info_path, pkginfo_path)
 
305
 
 
306
    def license_file(self):
 
307
        """Return license filename from a license-file key in setup.cfg, or None."""
 
308
        metadata = self.distribution.get_option_dict('metadata')
 
309
        if not 'license_file' in metadata:
 
310
            return None
 
311
        return metadata['license_file'][1]
 
312
 
 
313
    def setupcfg_requirements(self):
 
314
        """Generate requirements from setup.cfg as
 
315
        ('Requires-Dist', 'requirement; qualifier') tuples. From a metadata
 
316
        section in setup.cfg:
 
317
 
 
318
        [metadata]
 
319
        provides-extra = extra1
 
320
            extra2
 
321
        requires-dist = requirement; qualifier
 
322
            another; qualifier2
 
323
            unqualified
 
324
 
 
325
        Yields
 
326
 
 
327
        ('Provides-Extra', 'extra1'),
 
328
        ('Provides-Extra', 'extra2'),
 
329
        ('Requires-Dist', 'requirement; qualifier'),
 
330
        ('Requires-Dist', 'another; qualifier2'),
 
331
        ('Requires-Dist', 'unqualified')
 
332
        """
 
333
        metadata = self.distribution.get_option_dict('metadata')
 
334
 
 
335
        # our .ini parser folds - to _ in key names:
 
336
        for key, title in (('provides_extra', 'Provides-Extra'),
 
337
                           ('requires_dist', 'Requires-Dist')):
 
338
            if not key in metadata:
 
339
                continue
 
340
            field = metadata[key]
 
341
            for line in field[1].splitlines():
 
342
                line = line.strip()
 
343
                if not line:
 
344
                    continue
 
345
                yield (title, line)
 
346
 
 
347
    def add_requirements(self, metadata_path):
 
348
        """Add additional requirements from setup.cfg to file metadata_path"""
 
349
        additional = list(self.setupcfg_requirements())
 
350
        if not additional: return
 
351
        pkg_info = read_pkg_info(metadata_path)
 
352
        if 'Provides-Extra' in pkg_info or 'Requires-Dist' in pkg_info:
 
353
            warnings.warn('setup.cfg requirements overwrite values from setup.py')
 
354
            del pkg_info['Provides-Extra']
 
355
            del pkg_info['Requires-Dist']
 
356
        for k, v in additional:
 
357
            pkg_info[k] = v
 
358
        write_pkg_info(metadata_path, pkg_info)
 
359
 
 
360
    def egg2dist(self, egginfo_path, distinfo_path):
 
361
        """Convert an .egg-info directory into a .dist-info directory"""
 
362
        def adios(p):
 
363
            """Appropriately delete directory, file or link."""
 
364
            if os.path.exists(p) and not os.path.islink(p) and os.path.isdir(p):
 
365
                shutil.rmtree(p)
 
366
            elif os.path.exists(p):
 
367
                os.unlink(p)
 
368
 
 
369
        adios(distinfo_path)
 
370
 
 
371
        if not os.path.exists(egginfo_path):
 
372
            # There is no egg-info. This is probably because the egg-info
 
373
            # file/directory is not named matching the distribution name used
 
374
            # to name the archive file. Check for this case and report
 
375
            # accordingly.
 
376
            import glob
 
377
            pat = os.path.join(os.path.dirname(egginfo_path), '*.egg-info')
 
378
            possible = glob.glob(pat)
 
379
            err = "Egg metadata expected at %s but not found" % (egginfo_path,)
 
380
            if possible:
 
381
                alt = os.path.basename(possible[0])
 
382
                err += " (%s found - possible misnamed archive file?)" % (alt,)
 
383
 
 
384
            raise ValueError(err)
 
385
 
 
386
        if os.path.isfile(egginfo_path):
 
387
            # .egg-info is a single file
 
388
            pkginfo_path = egginfo_path
 
389
            pkg_info = self._pkginfo_to_metadata(egginfo_path, egginfo_path)
 
390
            os.mkdir(distinfo_path)
 
391
        else:
 
392
            # .egg-info is a directory
 
393
            pkginfo_path = os.path.join(egginfo_path, 'PKG-INFO')
 
394
            pkg_info = self._pkginfo_to_metadata(egginfo_path, pkginfo_path)
 
395
 
 
396
            # ignore common egg metadata that is useless to wheel
 
397
            shutil.copytree(egginfo_path, distinfo_path,
 
398
                            ignore=lambda x, y: set(('PKG-INFO',
 
399
                                                     'requires.txt',
 
400
                                                     'SOURCES.txt',
 
401
                                                     'not-zip-safe',)))
 
402
 
 
403
            # delete dependency_links if it is only whitespace
 
404
            dependency_links_path = os.path.join(distinfo_path, 'dependency_links.txt')
 
405
            with open(dependency_links_path, 'r') as dependency_links_file:
 
406
                dependency_links = dependency_links_file.read().strip()
 
407
            if not dependency_links:
 
408
                adios(dependency_links_path)
 
409
 
 
410
        write_pkg_info(os.path.join(distinfo_path, 'METADATA'), pkg_info)
 
411
 
 
412
        # XXX deprecated. Still useful for current distribute/setuptools.
 
413
        metadata_path = os.path.join(distinfo_path, 'METADATA')
 
414
        self.add_requirements(metadata_path)
 
415
 
 
416
        # XXX intentionally a different path than the PEP.
 
417
        metadata_json_path = os.path.join(distinfo_path, 'metadata.json')
 
418
        pymeta = pkginfo_to_dict(metadata_path,
 
419
                                 distribution=self.distribution)
 
420
 
 
421
        if 'description' in pymeta:
 
422
            description_filename = 'DESCRIPTION.rst'
 
423
            description_text = pymeta.pop('description')
 
424
            description_path = os.path.join(distinfo_path,
 
425
                                            description_filename)
 
426
            with open(description_path, "wb") as description_file:
 
427
                description_file.write(description_text.encode('utf-8'))
 
428
            pymeta['extensions']['python.details']['document_names']['description'] = description_filename
 
429
 
 
430
        # XXX heuristically copy any LICENSE/LICENSE.txt?
 
431
        license = self.license_file()
 
432
        if license:
 
433
            license_filename = 'LICENSE.txt'
 
434
            shutil.copy(license, os.path.join(self.distinfo_dir, license_filename))
 
435
            pymeta['extensions']['python.details']['document_names']['license'] = license_filename
 
436
 
 
437
        with open(metadata_json_path, "w") as metadata_json:
 
438
            json.dump(pymeta, metadata_json, sort_keys=True)
 
439
 
 
440
        adios(egginfo_path)
 
441
 
 
442
    def write_record(self, bdist_dir, distinfo_dir):
 
443
        from .util import urlsafe_b64encode
 
444
 
 
445
        record_path = os.path.join(distinfo_dir, 'RECORD')
 
446
        record_relpath = os.path.relpath(record_path, bdist_dir)
 
447
 
 
448
        def walk():
 
449
            for dir, dirs, files in os.walk(bdist_dir):
 
450
                dirs.sort()
 
451
                for f in sorted(files):
 
452
                    yield os.path.join(dir, f)
 
453
 
 
454
        def skip(path):
 
455
            """Wheel hashes every possible file."""
 
456
            return (path == record_relpath)
 
457
 
 
458
        with open_for_csv(record_path, 'w+') as record_file:
 
459
            writer = csv.writer(record_file)
 
460
            for path in walk():
 
461
                relpath = os.path.relpath(path, bdist_dir)
 
462
                if skip(relpath):
 
463
                    hash = ''
 
464
                    size = ''
 
465
                else:
 
466
                    with open(path, 'rb') as f:
 
467
                        data = f.read()
 
468
                    digest = hashlib.sha256(data).digest()
 
469
                    hash = 'sha256=' + native(urlsafe_b64encode(digest))
 
470
                    size = len(data)
 
471
                record_path = os.path.relpath(
 
472
                    path, bdist_dir).replace(os.path.sep, '/')
 
473
                writer.writerow((record_path, hash, size))