From 1231a4e097e55c5ac793ddaedad23bfd610591e6 Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Thu, 19 May 2011 13:07:25 +0200 Subject: [PATCH] initial import of the packaging package in the standard library --- Lib/packaging/__init__.py | 17 + Lib/packaging/_trove.py | 552 +++++++ Lib/packaging/command/__init__.py | 56 + Lib/packaging/command/bdist.py | 141 ++ Lib/packaging/command/bdist_dumb.py | 137 ++ Lib/packaging/command/bdist_msi.py | 740 +++++++++ Lib/packaging/command/bdist_wininst.py | 342 ++++ Lib/packaging/command/build.py | 151 ++ Lib/packaging/command/build_clib.py | 198 +++ Lib/packaging/command/build_ext.py | 666 ++++++++ Lib/packaging/command/build_py.py | 410 +++++ Lib/packaging/command/build_scripts.py | 132 ++ Lib/packaging/command/check.py | 88 + Lib/packaging/command/clean.py | 76 + Lib/packaging/command/cmd.py | 440 +++++ Lib/packaging/command/command_template | 35 + Lib/packaging/command/config.py | 351 ++++ Lib/packaging/command/install_data.py | 79 + Lib/packaging/command/install_dist.py | 625 +++++++ Lib/packaging/command/install_distinfo.py | 175 ++ Lib/packaging/command/install_headers.py | 43 + Lib/packaging/command/install_lib.py | 222 +++ Lib/packaging/command/install_scripts.py | 59 + Lib/packaging/command/register.py | 282 ++++ Lib/packaging/command/sdist.py | 375 +++++ Lib/packaging/command/test.py | 81 + Lib/packaging/command/upload.py | 201 +++ Lib/packaging/command/upload_docs.py | 173 ++ Lib/packaging/command/wininst-10.0-amd64.exe | Bin 0 -> 222208 bytes Lib/packaging/command/wininst-10.0.exe | Bin 0 -> 190464 bytes Lib/packaging/command/wininst-6.0.exe | Bin 0 -> 61440 bytes Lib/packaging/command/wininst-7.1.exe | Bin 0 -> 65536 bytes Lib/packaging/command/wininst-8.0.exe | Bin 0 -> 61440 bytes Lib/packaging/command/wininst-9.0-amd64.exe | Bin 0 -> 223744 bytes Lib/packaging/command/wininst-9.0.exe | Bin 0 -> 196096 bytes Lib/packaging/compat.py | 57 + Lib/packaging/compiler/__init__.py | 282 ++++ Lib/packaging/compiler/bcppcompiler.py | 356 ++++ Lib/packaging/compiler/ccompiler.py | 868 ++++++++++ Lib/packaging/compiler/cygwinccompiler.py | 355 ++++ Lib/packaging/compiler/extension.py | 121 ++ Lib/packaging/compiler/msvc9compiler.py | 720 ++++++++ Lib/packaging/compiler/msvccompiler.py | 636 ++++++++ Lib/packaging/compiler/unixccompiler.py | 339 ++++ Lib/packaging/config.py | 357 ++++ Lib/packaging/create.py | 693 ++++++++ Lib/packaging/database.py | 627 +++++++ Lib/packaging/depgraph.py | 270 +++ Lib/packaging/dist.py | 819 ++++++++++ Lib/packaging/errors.py | 142 ++ Lib/packaging/fancy_getopt.py | 451 +++++ Lib/packaging/install.py | 483 ++++++ Lib/packaging/manifest.py | 372 +++++ Lib/packaging/markers.py | 187 +++ Lib/packaging/metadata.py | 552 +++++++ Lib/packaging/pypi/__init__.py | 9 + Lib/packaging/pypi/base.py | 48 + Lib/packaging/pypi/dist.py | 547 +++++++ Lib/packaging/pypi/errors.py | 39 + Lib/packaging/pypi/mirrors.py | 52 + Lib/packaging/pypi/simple.py | 452 +++++ Lib/packaging/pypi/wrapper.py | 99 ++ Lib/packaging/pypi/xmlrpc.py | 200 +++ Lib/packaging/resources.py | 25 + Lib/packaging/run.py | 645 ++++++++ Lib/packaging/tests/LONG_DESC.txt | 44 + Lib/packaging/tests/PKG-INFO | 57 + Lib/packaging/tests/SETUPTOOLS-PKG-INFO | 182 +++ Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 | 183 +++ Lib/packaging/tests/__init__.py | 133 ++ Lib/packaging/tests/__main__.py | 20 + .../fake_dists/babar-0.1.dist-info/INSTALLER | 0 .../fake_dists/babar-0.1.dist-info/METADATA | 4 + .../fake_dists/babar-0.1.dist-info/RECORD | 0 .../fake_dists/babar-0.1.dist-info/REQUESTED | 0 .../fake_dists/babar-0.1.dist-info/RESOURCES | 2 + Lib/packaging/tests/fake_dists/babar.cfg | 1 + Lib/packaging/tests/fake_dists/babar.png | 0 .../fake_dists/bacon-0.1.egg-info/PKG-INFO | 6 + .../banana-0.4.egg/EGG-INFO/PKG-INFO | 18 + .../banana-0.4.egg/EGG-INFO/SOURCES.txt | 0 .../EGG-INFO/dependency_links.txt | 1 + .../banana-0.4.egg/EGG-INFO/entry_points.txt | 3 + .../banana-0.4.egg/EGG-INFO/not-zip-safe | 1 + .../banana-0.4.egg/EGG-INFO/requires.txt | 6 + .../banana-0.4.egg/EGG-INFO/top_level.txt | 0 .../tests/fake_dists/cheese-2.0.2.egg-info | 5 + .../choxie-2.0.0.9.dist-info/INSTALLER | 0 .../choxie-2.0.0.9.dist-info/METADATA | 9 + .../choxie-2.0.0.9.dist-info/RECORD | 0 .../choxie-2.0.0.9.dist-info/REQUESTED | 0 .../choxie-2.0.0.9/choxie/__init__.py | 1 + .../choxie-2.0.0.9/choxie/chocolate.py | 10 + .../fake_dists/choxie-2.0.0.9/truffles.py | 5 + .../coconuts-aster-10.3.egg-info/PKG-INFO | 5 + .../grammar-1.0a4.dist-info/INSTALLER | 0 .../grammar-1.0a4.dist-info/METADATA | 5 + .../fake_dists/grammar-1.0a4.dist-info/RECORD | 0 .../grammar-1.0a4.dist-info/REQUESTED | 0 .../grammar-1.0a4/grammar/__init__.py | 1 + .../fake_dists/grammar-1.0a4/grammar/utils.py | 8 + .../fake_dists/nut-funkyversion.egg-info | 3 + .../tests/fake_dists/strawberry-0.6.egg | Bin 0 -> 1402 bytes .../towel_stuff-0.1.dist-info/INSTALLER | 0 .../towel_stuff-0.1.dist-info/METADATA | 7 + .../towel_stuff-0.1.dist-info/RECORD | 0 .../towel_stuff-0.1.dist-info/REQUESTED | 0 .../towel_stuff-0.1/towel_stuff/__init__.py | 18 + .../tests/fake_dists/truffles-5.0.egg-info | 3 + Lib/packaging/tests/fixer/__init__.py | 0 Lib/packaging/tests/fixer/fix_idioms.py | 134 ++ Lib/packaging/tests/pypi_server.py | 444 +++++ Lib/packaging/tests/pypi_test_server.py | 59 + .../source/f/foobar/foobar-0.1.tar.gz | Bin 0 -> 110 bytes .../simple/badmd5/badmd5-0.1.tar.gz | 0 .../simple/badmd5/index.html | 3 + .../simple/foobar/index.html | 3 + .../downloads_with_md5/simple/index.html | 2 + .../foo_bar_baz/simple/bar/index.html | 6 + .../foo_bar_baz/simple/baz/index.html | 6 + .../foo_bar_baz/simple/foo/index.html | 6 + .../pypiserver/foo_bar_baz/simple/index.html | 3 + .../pypiserver/project_list/simple/index.html | 5 + .../test_found_links/simple/foobar/index.html | 6 + .../test_found_links/simple/index.html | 1 + .../test_pypi_server/external/index.html | 1 + .../test_pypi_server/simple/index.html | 1 + .../with_externals/external/external.html | 3 + .../with_externals/simple/foobar/index.html | 4 + .../with_externals/simple/index.html | 1 + .../with_norel_links/external/homepage.html | 7 + .../with_norel_links/external/nonrel.html | 1 + .../with_norel_links/simple/foobar/index.html | 6 + .../with_norel_links/simple/index.html | 1 + .../simple/foobar/index.html | 4 + .../with_real_externals/simple/index.html | 1 + Lib/packaging/tests/support.py | 259 +++ Lib/packaging/tests/test_ccompiler.py | 15 + Lib/packaging/tests/test_command_bdist.py | 77 + .../tests/test_command_bdist_dumb.py | 103 ++ Lib/packaging/tests/test_command_bdist_msi.py | 25 + .../tests/test_command_bdist_wininst.py | 32 + Lib/packaging/tests/test_command_build.py | 55 + .../tests/test_command_build_clib.py | 141 ++ Lib/packaging/tests/test_command_build_ext.py | 353 ++++ Lib/packaging/tests/test_command_build_py.py | 124 ++ .../tests/test_command_build_scripts.py | 112 ++ Lib/packaging/tests/test_command_check.py | 131 ++ Lib/packaging/tests/test_command_clean.py | 48 + Lib/packaging/tests/test_command_cmd.py | 101 ++ Lib/packaging/tests/test_command_config.py | 76 + .../tests/test_command_install_data.py | 80 + .../tests/test_command_install_dist.py | 210 +++ .../tests/test_command_install_distinfo.py | 192 +++ .../tests/test_command_install_headers.py | 38 + .../tests/test_command_install_lib.py | 111 ++ .../tests/test_command_install_scripts.py | 78 + Lib/packaging/tests/test_command_register.py | 259 +++ Lib/packaging/tests/test_command_sdist.py | 407 +++++ Lib/packaging/tests/test_command_test.py | 225 +++ Lib/packaging/tests/test_command_upload.py | 157 ++ .../tests/test_command_upload_docs.py | 205 +++ Lib/packaging/tests/test_compiler.py | 66 + Lib/packaging/tests/test_config.py | 424 +++++ Lib/packaging/tests/test_create.py | 235 +++ Lib/packaging/tests/test_cygwinccompiler.py | 88 + Lib/packaging/tests/test_database.py | 506 ++++++ Lib/packaging/tests/test_depgraph.py | 301 ++++ Lib/packaging/tests/test_dist.py | 445 +++++ Lib/packaging/tests/test_extension.py | 15 + Lib/packaging/tests/test_install.py | 353 ++++ Lib/packaging/tests/test_manifest.py | 72 + Lib/packaging/tests/test_markers.py | 71 + Lib/packaging/tests/test_metadata.py | 279 ++++ Lib/packaging/tests/test_mixin2to3.py | 75 + Lib/packaging/tests/test_msvc9compiler.py | 140 ++ Lib/packaging/tests/test_pypi_dist.py | 277 ++++ Lib/packaging/tests/test_pypi_server.py | 81 + Lib/packaging/tests/test_pypi_simple.py | 326 ++++ Lib/packaging/tests/test_pypi_xmlrpc.py | 93 ++ Lib/packaging/tests/test_resources.py | 168 ++ Lib/packaging/tests/test_run.py | 62 + Lib/packaging/tests/test_uninstall.py | 99 ++ Lib/packaging/tests/test_unixccompiler.py | 132 ++ Lib/packaging/tests/test_util.py | 928 +++++++++++ Lib/packaging/tests/test_version.py | 252 +++ Lib/packaging/util.py | 1451 +++++++++++++++++ Lib/packaging/version.py | 449 +++++ Lib/sysconfig.cfg | 111 ++ Lib/sysconfig.py | 277 ++-- Lib/test/test_packaging.py | 5 + Lib/test/test_sysconfig.py | 27 +- Tools/scripts/pysetup3 | 4 + 193 files changed, 30376 insertions(+), 149 deletions(-) create mode 100644 Lib/packaging/__init__.py create mode 100644 Lib/packaging/_trove.py create mode 100644 Lib/packaging/command/__init__.py create mode 100644 Lib/packaging/command/bdist.py create mode 100644 Lib/packaging/command/bdist_dumb.py create mode 100644 Lib/packaging/command/bdist_msi.py create mode 100644 Lib/packaging/command/bdist_wininst.py create mode 100644 Lib/packaging/command/build.py create mode 100644 Lib/packaging/command/build_clib.py create mode 100644 Lib/packaging/command/build_ext.py create mode 100644 Lib/packaging/command/build_py.py create mode 100644 Lib/packaging/command/build_scripts.py create mode 100644 Lib/packaging/command/check.py create mode 100644 Lib/packaging/command/clean.py create mode 100644 Lib/packaging/command/cmd.py create mode 100644 Lib/packaging/command/command_template create mode 100644 Lib/packaging/command/config.py create mode 100644 Lib/packaging/command/install_data.py create mode 100644 Lib/packaging/command/install_dist.py create mode 100644 Lib/packaging/command/install_distinfo.py create mode 100644 Lib/packaging/command/install_headers.py create mode 100644 Lib/packaging/command/install_lib.py create mode 100644 Lib/packaging/command/install_scripts.py create mode 100644 Lib/packaging/command/register.py create mode 100644 Lib/packaging/command/sdist.py create mode 100644 Lib/packaging/command/test.py create mode 100644 Lib/packaging/command/upload.py create mode 100644 Lib/packaging/command/upload_docs.py create mode 100644 Lib/packaging/command/wininst-10.0-amd64.exe create mode 100644 Lib/packaging/command/wininst-10.0.exe create mode 100644 Lib/packaging/command/wininst-6.0.exe create mode 100644 Lib/packaging/command/wininst-7.1.exe create mode 100644 Lib/packaging/command/wininst-8.0.exe create mode 100644 Lib/packaging/command/wininst-9.0-amd64.exe create mode 100644 Lib/packaging/command/wininst-9.0.exe create mode 100644 Lib/packaging/compat.py create mode 100644 Lib/packaging/compiler/__init__.py create mode 100644 Lib/packaging/compiler/bcppcompiler.py create mode 100644 Lib/packaging/compiler/ccompiler.py create mode 100644 Lib/packaging/compiler/cygwinccompiler.py create mode 100644 Lib/packaging/compiler/extension.py create mode 100644 Lib/packaging/compiler/msvc9compiler.py create mode 100644 Lib/packaging/compiler/msvccompiler.py create mode 100644 Lib/packaging/compiler/unixccompiler.py create mode 100644 Lib/packaging/config.py create mode 100644 Lib/packaging/create.py create mode 100644 Lib/packaging/database.py create mode 100644 Lib/packaging/depgraph.py create mode 100644 Lib/packaging/dist.py create mode 100644 Lib/packaging/errors.py create mode 100644 Lib/packaging/fancy_getopt.py create mode 100644 Lib/packaging/install.py create mode 100644 Lib/packaging/manifest.py create mode 100644 Lib/packaging/markers.py create mode 100644 Lib/packaging/metadata.py create mode 100644 Lib/packaging/pypi/__init__.py create mode 100644 Lib/packaging/pypi/base.py create mode 100644 Lib/packaging/pypi/dist.py create mode 100644 Lib/packaging/pypi/errors.py create mode 100644 Lib/packaging/pypi/mirrors.py create mode 100644 Lib/packaging/pypi/simple.py create mode 100644 Lib/packaging/pypi/wrapper.py create mode 100644 Lib/packaging/pypi/xmlrpc.py create mode 100644 Lib/packaging/resources.py create mode 100644 Lib/packaging/run.py create mode 100644 Lib/packaging/tests/LONG_DESC.txt create mode 100644 Lib/packaging/tests/PKG-INFO create mode 100644 Lib/packaging/tests/SETUPTOOLS-PKG-INFO create mode 100644 Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 create mode 100644 Lib/packaging/tests/__init__.py create mode 100644 Lib/packaging/tests/__main__.py create mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER create mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA create mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD create mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED create mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES create mode 100644 Lib/packaging/tests/fake_dists/babar.cfg create mode 100644 Lib/packaging/tests/fake_dists/babar.png create mode 100644 Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt create mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt create mode 100644 Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py create mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py create mode 100644 Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py create mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py create mode 100644 Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info create mode 100644 Lib/packaging/tests/fake_dists/strawberry-0.6.egg create mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER create mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA create mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD create mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED create mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py create mode 100644 Lib/packaging/tests/fake_dists/truffles-5.0.egg-info create mode 100644 Lib/packaging/tests/fixer/__init__.py create mode 100644 Lib/packaging/tests/fixer/fix_idioms.py create mode 100644 Lib/packaging/tests/pypi_server.py create mode 100644 Lib/packaging/tests/pypi_test_server.py create mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz create mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz create mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html create mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html create mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html create mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html create mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html create mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/project_list/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html create mode 100644 Lib/packaging/tests/pypiserver/test_found_links/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html create mode 100644 Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_externals/external/external.html create mode 100644 Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_externals/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html create mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html create mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html create mode 100644 Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html create mode 100644 Lib/packaging/tests/support.py create mode 100644 Lib/packaging/tests/test_ccompiler.py create mode 100644 Lib/packaging/tests/test_command_bdist.py create mode 100644 Lib/packaging/tests/test_command_bdist_dumb.py create mode 100644 Lib/packaging/tests/test_command_bdist_msi.py create mode 100644 Lib/packaging/tests/test_command_bdist_wininst.py create mode 100644 Lib/packaging/tests/test_command_build.py create mode 100644 Lib/packaging/tests/test_command_build_clib.py create mode 100644 Lib/packaging/tests/test_command_build_ext.py create mode 100644 Lib/packaging/tests/test_command_build_py.py create mode 100644 Lib/packaging/tests/test_command_build_scripts.py create mode 100644 Lib/packaging/tests/test_command_check.py create mode 100644 Lib/packaging/tests/test_command_clean.py create mode 100644 Lib/packaging/tests/test_command_cmd.py create mode 100644 Lib/packaging/tests/test_command_config.py create mode 100644 Lib/packaging/tests/test_command_install_data.py create mode 100644 Lib/packaging/tests/test_command_install_dist.py create mode 100644 Lib/packaging/tests/test_command_install_distinfo.py create mode 100644 Lib/packaging/tests/test_command_install_headers.py create mode 100644 Lib/packaging/tests/test_command_install_lib.py create mode 100644 Lib/packaging/tests/test_command_install_scripts.py create mode 100644 Lib/packaging/tests/test_command_register.py create mode 100644 Lib/packaging/tests/test_command_sdist.py create mode 100644 Lib/packaging/tests/test_command_test.py create mode 100644 Lib/packaging/tests/test_command_upload.py create mode 100644 Lib/packaging/tests/test_command_upload_docs.py create mode 100644 Lib/packaging/tests/test_compiler.py create mode 100644 Lib/packaging/tests/test_config.py create mode 100644 Lib/packaging/tests/test_create.py create mode 100644 Lib/packaging/tests/test_cygwinccompiler.py create mode 100644 Lib/packaging/tests/test_database.py create mode 100644 Lib/packaging/tests/test_depgraph.py create mode 100644 Lib/packaging/tests/test_dist.py create mode 100644 Lib/packaging/tests/test_extension.py create mode 100644 Lib/packaging/tests/test_install.py create mode 100644 Lib/packaging/tests/test_manifest.py create mode 100644 Lib/packaging/tests/test_markers.py create mode 100644 Lib/packaging/tests/test_metadata.py create mode 100644 Lib/packaging/tests/test_mixin2to3.py create mode 100644 Lib/packaging/tests/test_msvc9compiler.py create mode 100644 Lib/packaging/tests/test_pypi_dist.py create mode 100644 Lib/packaging/tests/test_pypi_server.py create mode 100644 Lib/packaging/tests/test_pypi_simple.py create mode 100644 Lib/packaging/tests/test_pypi_xmlrpc.py create mode 100644 Lib/packaging/tests/test_resources.py create mode 100644 Lib/packaging/tests/test_run.py create mode 100644 Lib/packaging/tests/test_uninstall.py create mode 100644 Lib/packaging/tests/test_unixccompiler.py create mode 100644 Lib/packaging/tests/test_util.py create mode 100644 Lib/packaging/tests/test_version.py create mode 100644 Lib/packaging/util.py create mode 100644 Lib/packaging/version.py create mode 100644 Lib/sysconfig.cfg create mode 100644 Lib/test/test_packaging.py create mode 100755 Tools/scripts/pysetup3 diff --git a/Lib/packaging/__init__.py b/Lib/packaging/__init__.py new file mode 100644 index 0000000000..93b611765c --- /dev/null +++ b/Lib/packaging/__init__.py @@ -0,0 +1,17 @@ +"""Support for packaging, distribution and installation of Python projects. + +Third-party tools can use parts of packaging as building blocks +without causing the other modules to be imported: + + import packaging.version + import packaging.metadata + import packaging.pypi.simple + import packaging.tests.pypi_server +""" + +from logging import getLogger + +__all__ = ['__version__', 'logger'] + +__version__ = "1.0a3" +logger = getLogger('packaging') diff --git a/Lib/packaging/_trove.py b/Lib/packaging/_trove.py new file mode 100644 index 0000000000..9a8719ce68 --- /dev/null +++ b/Lib/packaging/_trove.py @@ -0,0 +1,552 @@ +"""Temporary helper for create.""" + +# XXX get the list from PyPI and cache it instead of hardcoding + +# XXX see if it would be more useful to store it as another structure +# than a list of strings + +all_classifiers = [ +'Development Status :: 1 - Planning', +'Development Status :: 2 - Pre-Alpha', +'Development Status :: 3 - Alpha', +'Development Status :: 4 - Beta', +'Development Status :: 5 - Production/Stable', +'Development Status :: 6 - Mature', +'Development Status :: 7 - Inactive', +'Environment :: Console', +'Environment :: Console :: Curses', +'Environment :: Console :: Framebuffer', +'Environment :: Console :: Newt', +'Environment :: Console :: svgalib', +"Environment :: Handhelds/PDA's", +'Environment :: MacOS X', +'Environment :: MacOS X :: Aqua', +'Environment :: MacOS X :: Carbon', +'Environment :: MacOS X :: Cocoa', +'Environment :: No Input/Output (Daemon)', +'Environment :: Other Environment', +'Environment :: Plugins', +'Environment :: Web Environment', +'Environment :: Web Environment :: Buffet', +'Environment :: Web Environment :: Mozilla', +'Environment :: Web Environment :: ToscaWidgets', +'Environment :: Win32 (MS Windows)', +'Environment :: X11 Applications', +'Environment :: X11 Applications :: Gnome', +'Environment :: X11 Applications :: GTK', +'Environment :: X11 Applications :: KDE', +'Environment :: X11 Applications :: Qt', +'Framework :: BFG', +'Framework :: Buildout', +'Framework :: Chandler', +'Framework :: CubicWeb', +'Framework :: Django', +'Framework :: IDLE', +'Framework :: Paste', +'Framework :: Plone', +'Framework :: Pylons', +'Framework :: Setuptools Plugin', +'Framework :: Trac', +'Framework :: TurboGears', +'Framework :: TurboGears :: Applications', +'Framework :: TurboGears :: Widgets', +'Framework :: Twisted', +'Framework :: ZODB', +'Framework :: Zope2', +'Framework :: Zope3', +'Intended Audience :: Customer Service', +'Intended Audience :: Developers', +'Intended Audience :: Education', +'Intended Audience :: End Users/Desktop', +'Intended Audience :: Financial and Insurance Industry', +'Intended Audience :: Healthcare Industry', +'Intended Audience :: Information Technology', +'Intended Audience :: Legal Industry', +'Intended Audience :: Manufacturing', +'Intended Audience :: Other Audience', +'Intended Audience :: Religion', +'Intended Audience :: Science/Research', +'Intended Audience :: System Administrators', +'Intended Audience :: Telecommunications Industry', +'License :: Aladdin Free Public License (AFPL)', +'License :: DFSG approved', +'License :: Eiffel Forum License (EFL)', +'License :: Free For Educational Use', +'License :: Free For Home Use', +'License :: Free for non-commercial use', +'License :: Freely Distributable', +'License :: Free To Use But Restricted', +'License :: Freeware', +'License :: Netscape Public License (NPL)', +'License :: Nokia Open Source License (NOKOS)', +'License :: OSI Approved', +'License :: OSI Approved :: Academic Free License (AFL)', +'License :: OSI Approved :: Apache Software License', +'License :: OSI Approved :: Apple Public Source License', +'License :: OSI Approved :: Artistic License', +'License :: OSI Approved :: Attribution Assurance License', +'License :: OSI Approved :: BSD License', +'License :: OSI Approved :: Common Public License', +'License :: OSI Approved :: Eiffel Forum License', +'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)', +'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)', +'License :: OSI Approved :: GNU Affero General Public License v3', +'License :: OSI Approved :: GNU Free Documentation License (FDL)', +'License :: OSI Approved :: GNU General Public License (GPL)', +'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', +'License :: OSI Approved :: IBM Public License', +'License :: OSI Approved :: Intel Open Source License', +'License :: OSI Approved :: ISC License (ISCL)', +'License :: OSI Approved :: Jabber Open Source License', +'License :: OSI Approved :: MIT License', +'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)', +'License :: OSI Approved :: Motosoto License', +'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)', +'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)', +'License :: OSI Approved :: Nethack General Public License', +'License :: OSI Approved :: Nokia Open Source License', +'License :: OSI Approved :: Open Group Test Suite License', +'License :: OSI Approved :: Python License (CNRI Python License)', +'License :: OSI Approved :: Python Software Foundation License', +'License :: OSI Approved :: Qt Public License (QPL)', +'License :: OSI Approved :: Ricoh Source Code Public License', +'License :: OSI Approved :: Sleepycat License', +'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)', +'License :: OSI Approved :: Sun Public License', +'License :: OSI Approved :: University of Illinois/NCSA Open Source License', +'License :: OSI Approved :: Vovida Software License 1.0', +'License :: OSI Approved :: W3C License', +'License :: OSI Approved :: X.Net License', +'License :: OSI Approved :: zlib/libpng License', +'License :: OSI Approved :: Zope Public License', +'License :: Other/Proprietary License', +'License :: Public Domain', +'License :: Repoze Public License', +'Natural Language :: Afrikaans', +'Natural Language :: Arabic', +'Natural Language :: Bengali', +'Natural Language :: Bosnian', +'Natural Language :: Bulgarian', +'Natural Language :: Catalan', +'Natural Language :: Chinese (Simplified)', +'Natural Language :: Chinese (Traditional)', +'Natural Language :: Croatian', +'Natural Language :: Czech', +'Natural Language :: Danish', +'Natural Language :: Dutch', +'Natural Language :: English', +'Natural Language :: Esperanto', +'Natural Language :: Finnish', +'Natural Language :: French', +'Natural Language :: German', +'Natural Language :: Greek', +'Natural Language :: Hebrew', +'Natural Language :: Hindi', +'Natural Language :: Hungarian', +'Natural Language :: Icelandic', +'Natural Language :: Indonesian', +'Natural Language :: Italian', +'Natural Language :: Japanese', +'Natural Language :: Javanese', +'Natural Language :: Korean', +'Natural Language :: Latin', +'Natural Language :: Latvian', +'Natural Language :: Macedonian', +'Natural Language :: Malay', +'Natural Language :: Marathi', +'Natural Language :: Norwegian', +'Natural Language :: Panjabi', +'Natural Language :: Persian', +'Natural Language :: Polish', +'Natural Language :: Portuguese', +'Natural Language :: Portuguese (Brazilian)', +'Natural Language :: Romanian', +'Natural Language :: Russian', +'Natural Language :: Serbian', +'Natural Language :: Slovak', +'Natural Language :: Slovenian', +'Natural Language :: Spanish', +'Natural Language :: Swedish', +'Natural Language :: Tamil', +'Natural Language :: Telugu', +'Natural Language :: Thai', +'Natural Language :: Turkish', +'Natural Language :: Ukranian', +'Natural Language :: Urdu', +'Natural Language :: Vietnamese', +'Operating System :: BeOS', +'Operating System :: MacOS', +'Operating System :: MacOS :: MacOS 9', +'Operating System :: MacOS :: MacOS X', +'Operating System :: Microsoft', +'Operating System :: Microsoft :: MS-DOS', +'Operating System :: Microsoft :: Windows', +'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier', +'Operating System :: Microsoft :: Windows :: Windows 95/98/2000', +'Operating System :: Microsoft :: Windows :: Windows CE', +'Operating System :: Microsoft :: Windows :: Windows NT/2000', +'Operating System :: OS/2', +'Operating System :: OS Independent', +'Operating System :: Other OS', +'Operating System :: PalmOS', +'Operating System :: PDA Systems', +'Operating System :: POSIX', +'Operating System :: POSIX :: AIX', +'Operating System :: POSIX :: BSD', +'Operating System :: POSIX :: BSD :: BSD/OS', +'Operating System :: POSIX :: BSD :: FreeBSD', +'Operating System :: POSIX :: BSD :: NetBSD', +'Operating System :: POSIX :: BSD :: OpenBSD', +'Operating System :: POSIX :: GNU Hurd', +'Operating System :: POSIX :: HP-UX', +'Operating System :: POSIX :: IRIX', +'Operating System :: POSIX :: Linux', +'Operating System :: POSIX :: Other', +'Operating System :: POSIX :: SCO', +'Operating System :: POSIX :: SunOS/Solaris', +'Operating System :: Unix', +'Programming Language :: Ada', +'Programming Language :: APL', +'Programming Language :: ASP', +'Programming Language :: Assembly', +'Programming Language :: Awk', +'Programming Language :: Basic', +'Programming Language :: C', +'Programming Language :: C#', +'Programming Language :: C++', +'Programming Language :: Cold Fusion', +'Programming Language :: Cython', +'Programming Language :: Delphi/Kylix', +'Programming Language :: Dylan', +'Programming Language :: Eiffel', +'Programming Language :: Emacs-Lisp', +'Programming Language :: Erlang', +'Programming Language :: Euler', +'Programming Language :: Euphoria', +'Programming Language :: Forth', +'Programming Language :: Fortran', +'Programming Language :: Haskell', +'Programming Language :: Java', +'Programming Language :: JavaScript', +'Programming Language :: Lisp', +'Programming Language :: Logo', +'Programming Language :: ML', +'Programming Language :: Modula', +'Programming Language :: Objective C', +'Programming Language :: Object Pascal', +'Programming Language :: OCaml', +'Programming Language :: Other', +'Programming Language :: Other Scripting Engines', +'Programming Language :: Pascal', +'Programming Language :: Perl', +'Programming Language :: PHP', +'Programming Language :: Pike', +'Programming Language :: Pliant', +'Programming Language :: PL/SQL', +'Programming Language :: PROGRESS', +'Programming Language :: Prolog', +'Programming Language :: Python', +'Programming Language :: Python :: 2', +'Programming Language :: Python :: 2.3', +'Programming Language :: Python :: 2.4', +'Programming Language :: Python :: 2.5', +'Programming Language :: Python :: 2.6', +'Programming Language :: Python :: 2.7', +'Programming Language :: Python :: 3', +'Programming Language :: Python :: 3.0', +'Programming Language :: Python :: 3.1', +'Programming Language :: Python :: 3.2', +'Programming Language :: REBOL', +'Programming Language :: Rexx', +'Programming Language :: Ruby', +'Programming Language :: Scheme', +'Programming Language :: Simula', +'Programming Language :: Smalltalk', +'Programming Language :: SQL', +'Programming Language :: Tcl', +'Programming Language :: Unix Shell', +'Programming Language :: Visual Basic', +'Programming Language :: XBasic', +'Programming Language :: YACC', +'Programming Language :: Zope', +'Topic :: Adaptive Technologies', +'Topic :: Artistic Software', +'Topic :: Communications', +'Topic :: Communications :: BBS', +'Topic :: Communications :: Chat', +'Topic :: Communications :: Chat :: AOL Instant Messenger', +'Topic :: Communications :: Chat :: ICQ', +'Topic :: Communications :: Chat :: Internet Relay Chat', +'Topic :: Communications :: Chat :: Unix Talk', +'Topic :: Communications :: Conferencing', +'Topic :: Communications :: Email', +'Topic :: Communications :: Email :: Address Book', +'Topic :: Communications :: Email :: Email Clients (MUA)', +'Topic :: Communications :: Email :: Filters', +'Topic :: Communications :: Email :: Mailing List Servers', +'Topic :: Communications :: Email :: Mail Transport Agents', +'Topic :: Communications :: Email :: Post-Office', +'Topic :: Communications :: Email :: Post-Office :: IMAP', +'Topic :: Communications :: Email :: Post-Office :: POP3', +'Topic :: Communications :: Fax', +'Topic :: Communications :: FIDO', +'Topic :: Communications :: File Sharing', +'Topic :: Communications :: File Sharing :: Gnutella', +'Topic :: Communications :: File Sharing :: Napster', +'Topic :: Communications :: Ham Radio', +'Topic :: Communications :: Internet Phone', +'Topic :: Communications :: Telephony', +'Topic :: Communications :: Usenet News', +'Topic :: Database', +'Topic :: Database :: Database Engines/Servers', +'Topic :: Database :: Front-Ends', +'Topic :: Desktop Environment', +'Topic :: Desktop Environment :: File Managers', +'Topic :: Desktop Environment :: Gnome', +'Topic :: Desktop Environment :: GNUstep', +'Topic :: Desktop Environment :: K Desktop Environment (KDE)', +'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes', +'Topic :: Desktop Environment :: PicoGUI', +'Topic :: Desktop Environment :: PicoGUI :: Applications', +'Topic :: Desktop Environment :: PicoGUI :: Themes', +'Topic :: Desktop Environment :: Screen Savers', +'Topic :: Desktop Environment :: Window Managers', +'Topic :: Desktop Environment :: Window Managers :: Afterstep', +'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes', +'Topic :: Desktop Environment :: Window Managers :: Applets', +'Topic :: Desktop Environment :: Window Managers :: Blackbox', +'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes', +'Topic :: Desktop Environment :: Window Managers :: CTWM', +'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes', +'Topic :: Desktop Environment :: Window Managers :: Enlightenment', +'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets', +'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15', +'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16', +'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17', +'Topic :: Desktop Environment :: Window Managers :: Fluxbox', +'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes', +'Topic :: Desktop Environment :: Window Managers :: FVWM', +'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes', +'Topic :: Desktop Environment :: Window Managers :: IceWM', +'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes', +'Topic :: Desktop Environment :: Window Managers :: MetaCity', +'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes', +'Topic :: Desktop Environment :: Window Managers :: Oroborus', +'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes', +'Topic :: Desktop Environment :: Window Managers :: Sawfish', +'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30', +'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30', +'Topic :: Desktop Environment :: Window Managers :: Waimea', +'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes', +'Topic :: Desktop Environment :: Window Managers :: Window Maker', +'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets', +'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes', +'Topic :: Desktop Environment :: Window Managers :: XFCE', +'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes', +'Topic :: Documentation', +'Topic :: Education', +'Topic :: Education :: Computer Aided Instruction (CAI)', +'Topic :: Education :: Testing', +'Topic :: Games/Entertainment', +'Topic :: Games/Entertainment :: Arcade', +'Topic :: Games/Entertainment :: Board Games', +'Topic :: Games/Entertainment :: First Person Shooters', +'Topic :: Games/Entertainment :: Fortune Cookies', +'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)', +'Topic :: Games/Entertainment :: Puzzle Games', +'Topic :: Games/Entertainment :: Real Time Strategy', +'Topic :: Games/Entertainment :: Role-Playing', +'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games', +'Topic :: Games/Entertainment :: Simulation', +'Topic :: Games/Entertainment :: Turn Based Strategy', +'Topic :: Home Automation', +'Topic :: Internet', +'Topic :: Internet :: File Transfer Protocol (FTP)', +'Topic :: Internet :: Finger', +'Topic :: Internet :: Log Analysis', +'Topic :: Internet :: Name Service (DNS)', +'Topic :: Internet :: Proxy Servers', +'Topic :: Internet :: WAP', +'Topic :: Internet :: WWW/HTTP', +'Topic :: Internet :: WWW/HTTP :: Browsers', +'Topic :: Internet :: WWW/HTTP :: Dynamic Content', +'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries', +'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards', +'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary', +'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters', +'Topic :: Internet :: WWW/HTTP :: HTTP Servers', +'Topic :: Internet :: WWW/HTTP :: Indexing/Search', +'Topic :: Internet :: WWW/HTTP :: Site Management', +'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking', +'Topic :: Internet :: WWW/HTTP :: WSGI', +'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', +'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware', +'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', +'Topic :: Internet :: Z39.50', +'Topic :: Multimedia', +'Topic :: Multimedia :: Graphics', +'Topic :: Multimedia :: Graphics :: 3D Modeling', +'Topic :: Multimedia :: Graphics :: 3D Rendering', +'Topic :: Multimedia :: Graphics :: Capture', +'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera', +'Topic :: Multimedia :: Graphics :: Capture :: Scanners', +'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture', +'Topic :: Multimedia :: Graphics :: Editors', +'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based', +'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', +'Topic :: Multimedia :: Graphics :: Graphics Conversion', +'Topic :: Multimedia :: Graphics :: Presentation', +'Topic :: Multimedia :: Graphics :: Viewers', +'Topic :: Multimedia :: Sound/Audio', +'Topic :: Multimedia :: Sound/Audio :: Analysis', +'Topic :: Multimedia :: Sound/Audio :: Capture/Recording', +'Topic :: Multimedia :: Sound/Audio :: CD Audio', +'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing', +'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping', +'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing', +'Topic :: Multimedia :: Sound/Audio :: Conversion', +'Topic :: Multimedia :: Sound/Audio :: Editors', +'Topic :: Multimedia :: Sound/Audio :: MIDI', +'Topic :: Multimedia :: Sound/Audio :: Mixers', +'Topic :: Multimedia :: Sound/Audio :: Players', +'Topic :: Multimedia :: Sound/Audio :: Players :: MP3', +'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis', +'Topic :: Multimedia :: Sound/Audio :: Speech', +'Topic :: Multimedia :: Video', +'Topic :: Multimedia :: Video :: Capture', +'Topic :: Multimedia :: Video :: Conversion', +'Topic :: Multimedia :: Video :: Display', +'Topic :: Multimedia :: Video :: Non-Linear Editor', +'Topic :: Office/Business', +'Topic :: Office/Business :: Financial', +'Topic :: Office/Business :: Financial :: Accounting', +'Topic :: Office/Business :: Financial :: Investment', +'Topic :: Office/Business :: Financial :: Point-Of-Sale', +'Topic :: Office/Business :: Financial :: Spreadsheet', +'Topic :: Office/Business :: Groupware', +'Topic :: Office/Business :: News/Diary', +'Topic :: Office/Business :: Office Suites', +'Topic :: Office/Business :: Scheduling', +'Topic :: Other/Nonlisted Topic', +'Topic :: Printing', +'Topic :: Religion', +'Topic :: Scientific/Engineering', +'Topic :: Scientific/Engineering :: Artificial Intelligence', +'Topic :: Scientific/Engineering :: Astronomy', +'Topic :: Scientific/Engineering :: Atmospheric Science', +'Topic :: Scientific/Engineering :: Bio-Informatics', +'Topic :: Scientific/Engineering :: Chemistry', +'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', +'Topic :: Scientific/Engineering :: GIS', +'Topic :: Scientific/Engineering :: Human Machine Interfaces', +'Topic :: Scientific/Engineering :: Image Recognition', +'Topic :: Scientific/Engineering :: Information Analysis', +'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', +'Topic :: Scientific/Engineering :: Mathematics', +'Topic :: Scientific/Engineering :: Medical Science Apps.', +'Topic :: Scientific/Engineering :: Physics', +'Topic :: Scientific/Engineering :: Visualization', +'Topic :: Security', +'Topic :: Security :: Cryptography', +'Topic :: Sociology', +'Topic :: Sociology :: Genealogy', +'Topic :: Sociology :: History', +'Topic :: Software Development', +'Topic :: Software Development :: Assemblers', +'Topic :: Software Development :: Bug Tracking', +'Topic :: Software Development :: Build Tools', +'Topic :: Software Development :: Code Generators', +'Topic :: Software Development :: Compilers', +'Topic :: Software Development :: Debuggers', +'Topic :: Software Development :: Disassemblers', +'Topic :: Software Development :: Documentation', +'Topic :: Software Development :: Embedded Systems', +'Topic :: Software Development :: Internationalization', +'Topic :: Software Development :: Interpreters', +'Topic :: Software Development :: Libraries', +'Topic :: Software Development :: Libraries :: Application Frameworks', +'Topic :: Software Development :: Libraries :: Java Libraries', +'Topic :: Software Development :: Libraries :: Perl Modules', +'Topic :: Software Development :: Libraries :: PHP Classes', +'Topic :: Software Development :: Libraries :: Pike Modules', +'Topic :: Software Development :: Libraries :: pygame', +'Topic :: Software Development :: Libraries :: Python Modules', +'Topic :: Software Development :: Libraries :: Ruby Modules', +'Topic :: Software Development :: Libraries :: Tcl Extensions', +'Topic :: Software Development :: Localization', +'Topic :: Software Development :: Object Brokering', +'Topic :: Software Development :: Object Brokering :: CORBA', +'Topic :: Software Development :: Pre-processors', +'Topic :: Software Development :: Quality Assurance', +'Topic :: Software Development :: Testing', +'Topic :: Software Development :: Testing :: Traffic Generation', +'Topic :: Software Development :: User Interfaces', +'Topic :: Software Development :: Version Control', +'Topic :: Software Development :: Version Control :: CVS', +'Topic :: Software Development :: Version Control :: RCS', +'Topic :: Software Development :: Version Control :: SCCS', +'Topic :: Software Development :: Widget Sets', +'Topic :: System', +'Topic :: System :: Archiving', +'Topic :: System :: Archiving :: Backup', +'Topic :: System :: Archiving :: Compression', +'Topic :: System :: Archiving :: Mirroring', +'Topic :: System :: Archiving :: Packaging', +'Topic :: System :: Benchmark', +'Topic :: System :: Boot', +'Topic :: System :: Boot :: Init', +'Topic :: System :: Clustering', +'Topic :: System :: Console Fonts', +'Topic :: System :: Distributed Computing', +'Topic :: System :: Emulators', +'Topic :: System :: Filesystems', +'Topic :: System :: Hardware', +'Topic :: System :: Hardware :: Hardware Drivers', +'Topic :: System :: Hardware :: Mainframes', +'Topic :: System :: Hardware :: Symmetric Multi-processing', +'Topic :: System :: Installation/Setup', +'Topic :: System :: Logging', +'Topic :: System :: Monitoring', +'Topic :: System :: Networking', +'Topic :: System :: Networking :: Firewalls', +'Topic :: System :: Networking :: Monitoring', +'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog', +'Topic :: System :: Networking :: Time Synchronization', +'Topic :: System :: Operating System', +'Topic :: System :: Operating System Kernels', +'Topic :: System :: Operating System Kernels :: BSD', +'Topic :: System :: Operating System Kernels :: GNU Hurd', +'Topic :: System :: Operating System Kernels :: Linux', +'Topic :: System :: Power (UPS)', +'Topic :: System :: Recovery Tools', +'Topic :: System :: Shells', +'Topic :: System :: Software Distribution', +'Topic :: System :: Systems Administration', +'Topic :: System :: Systems Administration :: Authentication/Directory', +'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP', +'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS', +'Topic :: System :: System Shells', +'Topic :: Terminals', +'Topic :: Terminals :: Serial', +'Topic :: Terminals :: Telnet', +'Topic :: Terminals :: Terminal Emulators/X Terminals', +'Topic :: Text Editors', +'Topic :: Text Editors :: Documentation', +'Topic :: Text Editors :: Emacs', +'Topic :: Text Editors :: Integrated Development Environments (IDE)', +'Topic :: Text Editors :: Text Processing', +'Topic :: Text Editors :: Word Processors', +'Topic :: Text Processing', +'Topic :: Text Processing :: Filters', +'Topic :: Text Processing :: Fonts', +'Topic :: Text Processing :: General', +'Topic :: Text Processing :: Indexing', +'Topic :: Text Processing :: Linguistic', +'Topic :: Text Processing :: Markup', +'Topic :: Text Processing :: Markup :: HTML', +'Topic :: Text Processing :: Markup :: LaTeX', +'Topic :: Text Processing :: Markup :: SGML', +'Topic :: Text Processing :: Markup :: VRML', +'Topic :: Text Processing :: Markup :: XML', +'Topic :: Utilities', +] diff --git a/Lib/packaging/command/__init__.py b/Lib/packaging/command/__init__.py new file mode 100644 index 0000000000..6a3785061b --- /dev/null +++ b/Lib/packaging/command/__init__.py @@ -0,0 +1,56 @@ +"""Subpackage containing all standard commands.""" + +from packaging.errors import PackagingModuleError +from packaging.util import resolve_name + +__all__ = ['get_command_names', 'set_command', 'get_command_class', + 'STANDARD_COMMANDS'] + +_COMMANDS = { + 'check': 'packaging.command.check.check', + 'test': 'packaging.command.test.test', + 'build': 'packaging.command.build.build', + 'build_py': 'packaging.command.build_py.build_py', + 'build_ext': 'packaging.command.build_ext.build_ext', + 'build_clib': 'packaging.command.build_clib.build_clib', + 'build_scripts': 'packaging.command.build_scripts.build_scripts', + 'clean': 'packaging.command.clean.clean', + 'install_dist': 'packaging.command.install_dist.install_dist', + 'install_lib': 'packaging.command.install_lib.install_lib', + 'install_headers': 'packaging.command.install_headers.install_headers', + 'install_scripts': 'packaging.command.install_scripts.install_scripts', + 'install_data': 'packaging.command.install_data.install_data', + 'install_distinfo': + 'packaging.command.install_distinfo.install_distinfo', + 'sdist': 'packaging.command.sdist.sdist', + 'bdist': 'packaging.command.bdist.bdist', + 'bdist_dumb': 'packaging.command.bdist_dumb.bdist_dumb', + 'bdist_wininst': 'packaging.command.bdist_wininst.bdist_wininst', + 'register': 'packaging.command.register.register', + 'upload': 'packaging.command.upload.upload', + 'upload_docs': 'packaging.command.upload_docs.upload_docs'} + +STANDARD_COMMANDS = set(_COMMANDS) + + +def get_command_names(): + """Return registered commands""" + return sorted(_COMMANDS) + + +def set_command(location): + cls = resolve_name(location) + # XXX we want to do the duck-type checking here + _COMMANDS[cls.get_command_name()] = cls + + +def get_command_class(name): + """Return the registered command""" + try: + cls = _COMMANDS[name] + if isinstance(cls, str): + cls = resolve_name(cls) + _COMMANDS[name] = cls + return cls + except KeyError: + raise PackagingModuleError("Invalid command %s" % name) diff --git a/Lib/packaging/command/bdist.py b/Lib/packaging/command/bdist.py new file mode 100644 index 0000000000..4338a970e8 --- /dev/null +++ b/Lib/packaging/command/bdist.py @@ -0,0 +1,141 @@ +"""Create a built (binary) distribution. + +If a --formats option was given on the command line, this command will +call the corresponding bdist_* commands; if the option was absent, a +bdist_* command depending on the current platform will be called. +""" + +import os + +from packaging import util +from packaging.command.cmd import Command +from packaging.errors import PackagingPlatformError, PackagingOptionError + + +def show_formats(): + """Print list of available formats (arguments to "--format" option). + """ + from packaging.fancy_getopt import FancyGetopt + formats = [] + for format in bdist.format_commands: + formats.append(("formats=" + format, None, + bdist.format_command[format][1])) + pretty_printer = FancyGetopt(formats) + pretty_printer.print_help("List of available distribution formats:") + + +class bdist(Command): + + description = "create a built (binary) distribution" + + user_options = [('bdist-base=', 'b', + "temporary directory for creating built distributions"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % util.get_platform()), + ('formats=', None, + "formats for distribution (comma-separated list)"), + ('dist-dir=', 'd', + "directory to put final built distributions in " + "[default: dist]"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ] + + boolean_options = ['skip-build'] + + help_options = [ + ('help-formats', None, + "lists available distribution formats", show_formats), + ] + + # This is of course very simplistic. The various UNIX family operating + # systems have their specific formats, but they are out of scope for us; + # bdist_dumb is, well, dumb; it's more a building block for other + # packaging tools than a real end-user binary format. + default_format = {'posix': 'gztar', + 'nt': 'zip', + 'os2': 'zip'} + + # Establish the preferred order (for the --help-formats option). + format_commands = ['gztar', 'bztar', 'ztar', 'tar', + 'wininst', 'zip', 'msi'] + + # And the real information. + format_command = {'gztar': ('bdist_dumb', "gzip'ed tar file"), + 'bztar': ('bdist_dumb', "bzip2'ed tar file"), + 'ztar': ('bdist_dumb', "compressed tar file"), + 'tar': ('bdist_dumb', "tar file"), + 'wininst': ('bdist_wininst', + "Windows executable installer"), + 'zip': ('bdist_dumb', "ZIP file"), + 'msi': ('bdist_msi', "Microsoft Installer") + } + + + def initialize_options(self): + self.bdist_base = None + self.plat_name = None + self.formats = None + self.dist_dir = None + self.skip_build = False + self.group = None + self.owner = None + + def finalize_options(self): + # have to finalize 'plat_name' before 'bdist_base' + if self.plat_name is None: + if self.skip_build: + self.plat_name = util.get_platform() + else: + self.plat_name = self.get_finalized_command('build').plat_name + + # 'bdist_base' -- parent of per-built-distribution-format + # temporary directories (eg. we'll probably have + # "build/bdist./dumb", etc.) + if self.bdist_base is None: + build_base = self.get_finalized_command('build').build_base + self.bdist_base = os.path.join(build_base, + 'bdist.' + self.plat_name) + + self.ensure_string_list('formats') + if self.formats is None: + try: + self.formats = [self.default_format[os.name]] + except KeyError: + raise PackagingPlatformError("don't know how to create built distributions " + \ + "on platform %s" % os.name) + + if self.dist_dir is None: + self.dist_dir = "dist" + + def run(self): + # Figure out which sub-commands we need to run. + commands = [] + for format in self.formats: + try: + commands.append(self.format_command[format][0]) + except KeyError: + raise PackagingOptionError("invalid format '%s'" % format) + + # Reinitialize and run each command. + for i in range(len(self.formats)): + cmd_name = commands[i] + sub_cmd = self.get_reinitialized_command(cmd_name) + + # passing the owner and group names for tar archiving + if cmd_name == 'bdist_dumb': + sub_cmd.owner = self.owner + sub_cmd.group = self.group + + # If we're going to need to run this command again, tell it to + # keep its temporary files around so subsequent runs go faster. + if cmd_name in commands[i+1:]: + sub_cmd.keep_temp = True + self.run_command(cmd_name) diff --git a/Lib/packaging/command/bdist_dumb.py b/Lib/packaging/command/bdist_dumb.py new file mode 100644 index 0000000000..f74b720925 --- /dev/null +++ b/Lib/packaging/command/bdist_dumb.py @@ -0,0 +1,137 @@ +"""Create a "dumb" built distribution. + +A dumb distribution is just an archive meant to be unpacked under +sys.prefix or sys.exec_prefix. +""" + +import os + +from shutil import rmtree +from sysconfig import get_python_version +from packaging.util import get_platform +from packaging.command.cmd import Command +from packaging.errors import PackagingPlatformError +from packaging import logger + +class bdist_dumb(Command): + + description = 'create a "dumb" built distribution' + + user_options = [('bdist-dir=', 'd', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('format=', 'f', + "archive format to create (tar, ztar, gztar, zip)"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('relative', None, + "build the archive using relative paths" + "(default: false)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ] + + boolean_options = ['keep-temp', 'skip-build', 'relative'] + + default_format = { 'posix': 'gztar', + 'nt': 'zip', + 'os2': 'zip' } + + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.format = None + self.keep_temp = False + self.dist_dir = None + self.skip_build = False + self.relative = False + self.owner = None + self.group = None + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'dumb') + + if self.format is None: + try: + self.format = self.default_format[os.name] + except KeyError: + raise PackagingPlatformError(("don't know how to create dumb built distributions " + + "on platform %s") % os.name) + + self.set_undefined_options('bdist', 'dist_dir', 'plat_name') + + def run(self): + if not self.skip_build: + self.run_command('build') + + install = self.get_reinitialized_command('install_dist', + reinit_subcommands=True) + install.root = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = False + + logger.info("installing to %s", self.bdist_dir) + self.run_command('install_dist') + + # And make an archive relative to the root of the + # pseudo-installation tree. + archive_basename = "%s.%s" % (self.distribution.get_fullname(), + self.plat_name) + + # OS/2 objects to any ":" characters in a filename (such as when + # a timestamp is used in a version) so change them to hyphens. + if os.name == "os2": + archive_basename = archive_basename.replace(":", "-") + + pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) + if not self.relative: + archive_root = self.bdist_dir + else: + if (self.distribution.has_ext_modules() and + (install.install_base != install.install_platbase)): + raise PackagingPlatformError( + "can't make a dumb built distribution where base and " + "platbase are different (%r, %r)" % + (install.install_base, install.install_platbase)) + else: + archive_root = os.path.join( + self.bdist_dir, + self._ensure_relative(install.install_base)) + + # Make the archive + filename = self.make_archive(pseudoinstall_root, + self.format, root_dir=archive_root, + owner=self.owner, group=self.group) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_dumb', pyversion, + filename)) + + if not self.keep_temp: + if self.dry_run: + logger.info('removing %s', self.bdist_dir) + else: + rmtree(self.bdist_dir) + + def _ensure_relative(self, path): + # copied from dir_util, deleted + drive, path = os.path.splitdrive(path) + if path[0:1] == os.sep: + path = drive + path[1:] + return path diff --git a/Lib/packaging/command/bdist_msi.py b/Lib/packaging/command/bdist_msi.py new file mode 100644 index 0000000000..493f8b34e3 --- /dev/null +++ b/Lib/packaging/command/bdist_msi.py @@ -0,0 +1,740 @@ +"""Create a Microsoft Installer (.msi) binary distribution.""" + +# Copyright (C) 2005, 2006 Martin von Löwis +# Licensed to PSF under a Contributor Agreement. + +import sys +import os +import msilib + + +from sysconfig import get_python_version +from shutil import rmtree +from packaging.command.cmd import Command +from packaging.version import NormalizedVersion +from packaging.errors import PackagingOptionError +from packaging import logger as log +from packaging.util import get_platform +from msilib import schema, sequence, text +from msilib import Directory, Feature, Dialog, add_data + +class MSIVersion(NormalizedVersion): + """ + MSI ProductVersion must be strictly numeric. + MSIVersion disallows prerelease and postrelease versions. + """ + def __init__(self, *args, **kwargs): + super(MSIVersion, self).__init__(*args, **kwargs) + if not self.is_final: + raise ValueError("ProductVersion must be strictly numeric") + +class PyDialog(Dialog): + """Dialog class with a fixed layout: controls at the top, then a ruler, + then a list of buttons: back, next, cancel. Optionally a bitmap at the + left.""" + def __init__(self, *args, **kw): + """Dialog(database, name, x, y, w, h, attributes, title, first, + default, cancel, bitmap=true)""" + Dialog.__init__(self, *args) + ruler = self.h - 36 + #if kw.get("bitmap", True): + # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") + self.line("BottomLine", 0, ruler, self.w, 0) + + def title(self, title): + "Set the title text of the dialog at the top." + # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, + # text, in VerdanaBold10 + self.text("Title", 15, 10, 320, 60, 0x30003, + r"{\VerdanaBold10}%s" % title) + + def back(self, title, next, name = "Back", active = 1): + """Add a back button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) + + def cancel(self, title, next, name = "Cancel", active = 1): + """Add a cancel button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) + + def next(self, title, next, name = "Next", active = 1): + """Add a Next button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) + + def xbutton(self, name, title, next, xpos): + """Add a button with a given title, the tab-next button, + its name in the Control table, giving its x position; the + y-position is aligned with the other buttons. + + Return the button, so that events can be associated""" + return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) + +class bdist_msi(Command): + + description = "create a Microsoft Installer (.msi) binary distribution" + + user_options = [('bdist-dir=', None, + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('target-version=', None, + "require a specific python version" + + " on the target system"), + ('no-target-compile', 'c', + "do not compile .py to .pyc on the target system"), + ('no-target-optimize', 'o', + "do not compile .py to .pyo (optimized)" + "on the target system"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('install-script=', None, + "basename of installation script to be run after" + "installation or before deinstallation"), + ('pre-install-script=', None, + "Fully qualified filename of a script to be run before " + "any files are installed. This script need not be in the " + "distribution"), + ] + + boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', + 'skip-build'] + + all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4', + '2.5', '2.6', '2.7', '2.8', '2.9', + '3.0', '3.1', '3.2', '3.3', '3.4', + '3.5', '3.6', '3.7', '3.8', '3.9'] + other_version = 'X' + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = False + self.no_target_compile = False + self.no_target_optimize = False + self.target_version = None + self.dist_dir = None + self.skip_build = False + self.install_script = None + self.pre_install_script = None + self.versions = None + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'msi') + short_version = get_python_version() + if (not self.target_version) and self.distribution.has_ext_modules(): + self.target_version = short_version + if self.target_version: + self.versions = [self.target_version] + if not self.skip_build and self.distribution.has_ext_modules()\ + and self.target_version != short_version: + raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \ + " option must be specified" % (short_version,)) + else: + self.versions = list(self.all_versions) + + self.set_undefined_options('bdist', 'dist_dir', 'plat_name') + + if self.pre_install_script: + raise PackagingOptionError("the pre-install-script feature is not yet implemented") + + if self.install_script: + for script in self.distribution.scripts: + if self.install_script == os.path.basename(script): + break + else: + raise PackagingOptionError("install_script '%s' not found in scripts" % \ + self.install_script) + self.install_script_key = None + + + def run(self): + if not self.skip_build: + self.run_command('build') + + install = self.get_reinitialized_command('install_dist', + reinit_subcommands=True) + install.prefix = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = False + + install_lib = self.get_reinitialized_command('install_lib') + # we do not want to include pyc or pyo files + install_lib.compile = False + install_lib.optimize = 0 + + if self.distribution.has_ext_modules(): + # If we are building an installer for a Python version other + # than the one we are currently running, then we need to ensure + # our build_lib reflects the other Python version rather than ours. + # Note that for target_version!=sys.version, we must have skipped the + # build step, so there is no issue with enforcing the build of this + # version. + target_version = self.target_version + if not target_version: + assert self.skip_build, "Should have already checked this" + target_version = sys.version[0:3] + plat_specifier = ".%s-%s" % (self.plat_name, target_version) + build = self.get_finalized_command('build') + build.build_lib = os.path.join(build.build_base, + 'lib' + plat_specifier) + + log.info("installing to %s", self.bdist_dir) + install.ensure_finalized() + + # avoid warning of 'install_lib' about installing + # into a directory not in sys.path + sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) + + install.run() + + del sys.path[0] + + self.mkpath(self.dist_dir) + fullname = self.distribution.get_fullname() + installer_name = self.get_installer_filename(fullname) + installer_name = os.path.abspath(installer_name) + if os.path.exists(installer_name): os.unlink(installer_name) + + metadata = self.distribution.metadata + author = metadata.author + if not author: + author = metadata.maintainer + if not author: + author = "UNKNOWN" + version = MSIVersion(metadata.get_version()) + # Prefix ProductName with Python x.y, so that + # it sorts together with the other Python packages + # in Add-Remove-Programs (APR) + fullname = self.distribution.get_fullname() + if self.target_version: + product_name = "Python %s %s" % (self.target_version, fullname) + else: + product_name = "Python %s" % (fullname) + self.db = msilib.init_database(installer_name, schema, + product_name, msilib.gen_uuid(), + str(version), author) + msilib.add_tables(self.db, sequence) + props = [('DistVersion', version)] + email = metadata.author_email or metadata.maintainer_email + if email: + props.append(("ARPCONTACT", email)) + if metadata.url: + props.append(("ARPURLINFOABOUT", metadata.url)) + if props: + add_data(self.db, 'Property', props) + + self.add_find_python() + self.add_files() + self.add_scripts() + self.add_ui() + self.db.Commit() + + if hasattr(self.distribution, 'dist_files'): + tup = 'bdist_msi', self.target_version or 'any', fullname + self.distribution.dist_files.append(tup) + + if not self.keep_temp: + log.info("removing temporary build directory %s", self.bdist_dir) + if not self.dry_run: + rmtree(self.bdist_dir) + + def add_files(self): + db = self.db + cab = msilib.CAB("distfiles") + rootdir = os.path.abspath(self.bdist_dir) + + root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir") + f = Feature(db, "Python", "Python", "Everything", + 0, 1, directory="TARGETDIR") + + items = [(f, root, '')] + for version in self.versions + [self.other_version]: + target = "TARGETDIR" + version + name = default = "Python" + version + desc = "Everything" + if version is self.other_version: + title = "Python from another location" + level = 2 + else: + title = "Python %s from registry" % version + level = 1 + f = Feature(db, name, title, desc, 1, level, directory=target) + dir = Directory(db, cab, root, rootdir, target, default) + items.append((f, dir, version)) + db.Commit() + + seen = {} + for feature, dir, version in items: + todo = [dir] + while todo: + dir = todo.pop() + for file in os.listdir(dir.absolute): + afile = os.path.join(dir.absolute, file) + if os.path.isdir(afile): + short = "%s|%s" % (dir.make_short(file), file) + default = file + version + newdir = Directory(db, cab, dir, file, default, short) + todo.append(newdir) + else: + if not dir.component: + dir.start_component(dir.logical, feature, 0) + if afile not in seen: + key = seen[afile] = dir.add_file(file) + if file==self.install_script: + if self.install_script_key: + raise PackagingOptionError( + "Multiple files with name %s" % file) + self.install_script_key = '[#%s]' % key + else: + key = seen[afile] + add_data(self.db, "DuplicateFile", + [(key + version, dir.component, key, None, dir.logical)]) + db.Commit() + cab.commit(db) + + def add_find_python(self): + """Adds code to the installer to compute the location of Python. + + Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the + registry for each version of Python. + + Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined, + else from PYTHON.MACHINE.X.Y. + + Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe""" + + start = 402 + for ver in self.versions: + install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver + machine_reg = "python.machine." + ver + user_reg = "python.user." + ver + machine_prop = "PYTHON.MACHINE." + ver + user_prop = "PYTHON.USER." + ver + machine_action = "PythonFromMachine" + ver + user_action = "PythonFromUser" + ver + exe_action = "PythonExe" + ver + target_dir_prop = "TARGETDIR" + ver + exe_prop = "PYTHON" + ver + if msilib.Win64: + # type: msidbLocatorTypeRawValue + msidbLocatorType64bit + Type = 2+16 + else: + Type = 2 + add_data(self.db, "RegLocator", + [(machine_reg, 2, install_path, None, Type), + (user_reg, 1, install_path, None, Type)]) + add_data(self.db, "AppSearch", + [(machine_prop, machine_reg), + (user_prop, user_reg)]) + add_data(self.db, "CustomAction", + [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"), + (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"), + (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"), + ]) + add_data(self.db, "InstallExecuteSequence", + [(machine_action, machine_prop, start), + (user_action, user_prop, start + 1), + (exe_action, None, start + 2), + ]) + add_data(self.db, "InstallUISequence", + [(machine_action, machine_prop, start), + (user_action, user_prop, start + 1), + (exe_action, None, start + 2), + ]) + add_data(self.db, "Condition", + [("Python" + ver, 0, "NOT TARGETDIR" + ver)]) + start += 4 + assert start < 500 + + def add_scripts(self): + if self.install_script: + start = 6800 + for ver in self.versions + [self.other_version]: + install_action = "install_script." + ver + exe_prop = "PYTHON" + ver + add_data(self.db, "CustomAction", + [(install_action, 50, exe_prop, self.install_script_key)]) + add_data(self.db, "InstallExecuteSequence", + [(install_action, "&Python%s=3" % ver, start)]) + start += 1 + # XXX pre-install scripts are currently refused in finalize_options() + # but if this feature is completed, it will also need to add + # entries for each version as the above code does + if self.pre_install_script: + scriptfn = os.path.join(self.bdist_dir, "preinstall.bat") + with open(scriptfn, "w") as f: + # The batch file will be executed with [PYTHON], so that %1 + # is the path to the Python interpreter; %0 will be the path + # of the batch file. + # rem =""" + # %1 %0 + # exit + # """ + # + f.write('rem ="""\n%1 %0\nexit\n"""\n') + with open(self.pre_install_script) as fp: + f.write(fp.read()) + add_data(self.db, "Binary", + [("PreInstall", msilib.Binary(scriptfn)), + ]) + add_data(self.db, "CustomAction", + [("PreInstall", 2, "PreInstall", None), + ]) + add_data(self.db, "InstallExecuteSequence", + [("PreInstall", "NOT Installed", 450), + ]) + + def add_ui(self): + db = self.db + x = y = 50 + w = 370 + h = 300 + title = "[ProductName] Setup" + + # see "Dialog Style Bits" + modal = 3 # visible | modal + modeless = 1 # visible + + # UI customization properties + add_data(db, "Property", + # See "DefaultUIFont Property" + [("DefaultUIFont", "DlgFont8"), + # See "ErrorDialog Style Bit" + ("ErrorDialog", "ErrorDlg"), + ("Progress1", "Install"), # modified in maintenance type dlg + ("Progress2", "installs"), + ("MaintenanceForm_Action", "Repair"), + # possible values: ALL, JUSTME + ("WhichUsers", "ALL") + ]) + + # Fonts, see "TextStyle Table" + add_data(db, "TextStyle", + [("DlgFont8", "Tahoma", 9, None, 0), + ("DlgFontBold8", "Tahoma", 8, None, 1), #bold + ("VerdanaBold10", "Verdana", 10, None, 1), + ("VerdanaRed9", "Verdana", 9, 255, 0), + ]) + + # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" + # Numbers indicate sequence; see sequence.py for how these action integrate + add_data(db, "InstallUISequence", + [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), + ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), + # In the user interface, assume all-users installation if privileged. + ("SelectFeaturesDlg", "Not Installed", 1230), + # XXX no support for resume installations yet + #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), + ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), + ("ProgressDlg", None, 1280)]) + + add_data(db, 'ActionText', text.ActionText) + add_data(db, 'UIText', text.UIText) + ##################################################################### + # Standard dialogs: FatalError, UserExit, ExitDialog + fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + fatal.title("[ProductName] Installer ended prematurely") + fatal.back("< Back", "Finish", active = 0) + fatal.cancel("Cancel", "Back", active = 0) + fatal.text("Description1", 15, 70, 320, 80, 0x30003, + "[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.") + fatal.text("Description2", 15, 155, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c=fatal.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Exit") + + user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + user_exit.title("[ProductName] Installer was interrupted") + user_exit.back("< Back", "Finish", active = 0) + user_exit.cancel("Cancel", "Back", active = 0) + user_exit.text("Description1", 15, 70, 320, 80, 0x30003, + "[ProductName] setup was interrupted. Your system has not been modified. " + "To install this program at a later time, please run the installation again.") + user_exit.text("Description2", 15, 155, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c = user_exit.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Exit") + + exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + exit_dialog.title("Completing the [ProductName] Installer") + exit_dialog.back("< Back", "Finish", active = 0) + exit_dialog.cancel("Cancel", "Back", active = 0) + exit_dialog.text("Description", 15, 235, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c = exit_dialog.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Return") + + ##################################################################### + # Required dialog: FilesInUse, ErrorDlg + inuse = PyDialog(db, "FilesInUse", + x, y, w, h, + 19, # KeepModeless|Modal|Visible + title, + "Retry", "Retry", "Retry", bitmap=False) + inuse.text("Title", 15, 6, 200, 15, 0x30003, + r"{\DlgFontBold8}Files in Use") + inuse.text("Description", 20, 23, 280, 20, 0x30003, + "Some files that need to be updated are currently in use.") + inuse.text("Text", 20, 55, 330, 50, 3, + "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") + inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", + None, None, None) + c=inuse.back("Exit", "Ignore", name="Exit") + c.event("EndDialog", "Exit") + c=inuse.next("Ignore", "Retry", name="Ignore") + c.event("EndDialog", "Ignore") + c=inuse.cancel("Retry", "Exit", name="Retry") + c.event("EndDialog","Retry") + + # See "Error Dialog". See "ICE20" for the required names of the controls. + error = Dialog(db, "ErrorDlg", + 50, 10, 330, 101, + 65543, # Error|Minimize|Modal|Visible + title, + "ErrorText", None, None) + error.text("ErrorText", 50,9,280,48,3, "") + #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) + error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") + error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") + error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") + error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") + error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") + error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") + error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") + + ##################################################################### + # Global "Query Cancel" dialog + cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, + "No", "No", "No") + cancel.text("Text", 48, 15, 194, 30, 3, + "Are you sure you want to cancel [ProductName] installation?") + #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, + # "py.ico", None, None) + c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") + c.event("EndDialog", "Exit") + + c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") + c.event("EndDialog", "Return") + + ##################################################################### + # Global "Wait for costing" dialog + costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, + "Return", "Return", "Return") + costing.text("Text", 48, 15, 194, 30, 3, + "Please wait while the installer finishes determining your disk space requirements.") + c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) + c.event("EndDialog", "Exit") + + ##################################################################### + # Preparation dialog: no user input except cancellation + prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, + "Cancel", "Cancel", "Cancel") + prep.text("Description", 15, 70, 320, 40, 0x30003, + "Please wait while the Installer prepares to guide you through the installation.") + prep.title("Welcome to the [ProductName] Installer") + c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...") + c.mapping("ActionText", "Text") + c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None) + c.mapping("ActionData", "Text") + prep.back("Back", None, active=0) + prep.next("Next", None, active=0) + c=prep.cancel("Cancel", None) + c.event("SpawnDialog", "CancelDlg") + + ##################################################################### + # Feature (Python directory) selection + seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title, + "Next", "Next", "Cancel") + seldlg.title("Select Python Installations") + + seldlg.text("Hint", 15, 30, 300, 20, 3, + "Select the Python locations where %s should be installed." + % self.distribution.get_fullname()) + + seldlg.back("< Back", None, active=0) + c = seldlg.next("Next >", "Cancel") + order = 1 + c.event("[TARGETDIR]", "[SourceDir]", ordering=order) + for version in self.versions + [self.other_version]: + order += 1 + c.event("[TARGETDIR]", "[TARGETDIR%s]" % version, + "FEATURE_SELECTED AND &Python%s=3" % version, + ordering=order) + c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1) + c.event("EndDialog", "Return", ordering=order + 2) + c = seldlg.cancel("Cancel", "Features") + c.event("SpawnDialog", "CancelDlg") + + c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3, + "FEATURE", None, "PathEdit", None) + c.event("[FEATURE_SELECTED]", "1") + ver = self.other_version + install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver + dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver + + c = seldlg.text("Other", 15, 200, 300, 15, 3, + "Provide an alternate Python location") + c.condition("Enable", install_other_cond) + c.condition("Show", install_other_cond) + c.condition("Disable", dont_install_other_cond) + c.condition("Hide", dont_install_other_cond) + + c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1, + "TARGETDIR" + ver, None, "Next", None) + c.condition("Enable", install_other_cond) + c.condition("Show", install_other_cond) + c.condition("Disable", dont_install_other_cond) + c.condition("Hide", dont_install_other_cond) + + ##################################################################### + # Disk cost + cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, + "OK", "OK", "OK", bitmap=False) + cost.text("Title", 15, 6, 200, 15, 0x30003, + "{\DlgFontBold8}Disk Space Requirements") + cost.text("Description", 20, 20, 280, 20, 0x30003, + "The disk space required for the installation of the selected features.") + cost.text("Text", 20, 53, 330, 60, 3, + "The highlighted volumes (if any) do not have enough disk space " + "available for the currently selected features. You can either " + "remove some files from the highlighted volumes, or choose to " + "install less features onto local drive(s), or select different " + "destination drive(s).") + cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, + None, "{120}{70}{70}{70}{70}", None, None) + cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") + + ##################################################################### + # WhichUsers Dialog. Only available on NT, and for privileged users. + # This must be run before FindRelatedProducts, because that will + # take into account whether the previous installation was per-user + # or per-machine. We currently don't support going back to this + # dialog after "Next" was selected; to support this, we would need to + # find how to reset the ALLUSERS property, and how to re-run + # FindRelatedProducts. + # On Windows9x, the ALLUSERS property is ignored on the command line + # and in the Property table, but installer fails according to the documentation + # if a dialog attempts to set ALLUSERS. + whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, + "AdminInstall", "Next", "Cancel") + whichusers.title("Select whether to install [ProductName] for all users of this computer.") + # A radio group with two options: allusers, justme + g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3, + "WhichUsers", "", "Next") + g.add("ALL", 0, 5, 150, 20, "Install for all users") + g.add("JUSTME", 0, 25, 150, 20, "Install just for me") + + whichusers.back("Back", None, active=0) + + c = whichusers.next("Next >", "Cancel") + c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) + c.event("EndDialog", "Return", ordering = 2) + + c = whichusers.cancel("Cancel", "AdminInstall") + c.event("SpawnDialog", "CancelDlg") + + ##################################################################### + # Installation Progress dialog (modeless) + progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, + "Cancel", "Cancel", "Cancel", bitmap=False) + progress.text("Title", 20, 15, 200, 15, 0x30003, + "{\DlgFontBold8}[Progress1] [ProductName]") + progress.text("Text", 35, 65, 300, 30, 3, + "Please wait while the Installer [Progress2] [ProductName]. " + "This may take several minutes.") + progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") + + c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") + c.mapping("ActionText", "Text") + + #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) + #c.mapping("ActionData", "Text") + + c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, + None, "Progress done", None, None) + c.mapping("SetProgress", "Progress") + + progress.back("< Back", "Next", active=False) + progress.next("Next >", "Cancel", active=False) + progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") + + ################################################################### + # Maintenance type: repair/uninstall + maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, + "Next", "Next", "Cancel") + maint.title("Welcome to the [ProductName] Setup Wizard") + maint.text("BodyText", 15, 63, 330, 42, 3, + "Select whether you want to repair or remove [ProductName].") + g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3, + "MaintenanceForm_Action", "", "Next") + #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") + g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") + g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") + + maint.back("< Back", None, active=False) + c=maint.next("Finish", "Cancel") + # Change installation: Change progress dialog to "Change", then ask + # for feature selection + #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) + #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) + + # Reinstall: Change progress dialog to "Repair", then invoke reinstall + # Also set list of reinstalled features to "ALL" + c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) + c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) + c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) + c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) + + # Uninstall: Change progress to "Remove", then invoke uninstall + # Also set list of removed features to "ALL" + c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) + c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) + c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) + c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) + + # Close dialog when maintenance action scheduled + c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) + #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) + + maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") + + def get_installer_filename(self, fullname): + # Factored out to allow overriding in subclasses + if self.target_version: + base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name, + self.target_version) + else: + base_name = "%s.%s.msi" % (fullname, self.plat_name) + installer_name = os.path.join(self.dist_dir, base_name) + return installer_name diff --git a/Lib/packaging/command/bdist_wininst.py b/Lib/packaging/command/bdist_wininst.py new file mode 100644 index 0000000000..dbb74eaead --- /dev/null +++ b/Lib/packaging/command/bdist_wininst.py @@ -0,0 +1,342 @@ +"""Create an executable installer for Windows.""" + +# FIXME synchronize bytes/str use with same file in distutils + +import sys +import os + +from shutil import rmtree +from sysconfig import get_python_version +from packaging.command.cmd import Command +from packaging.errors import PackagingOptionError, PackagingPlatformError +from packaging import logger +from packaging.util import get_platform + + +class bdist_wininst(Command): + + description = "create an executable installer for Windows" + + user_options = [('bdist-dir=', None, + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('target-version=', None, + "require a specific python version" + + " on the target system"), + ('no-target-compile', 'c', + "do not compile .py to .pyc on the target system"), + ('no-target-optimize', 'o', + "do not compile .py to .pyo (optimized)" + "on the target system"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('bitmap=', 'b', + "bitmap to use for the installer instead of python-powered logo"), + ('title=', 't', + "title to display on the installer background instead of default"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('install-script=', None, + "basename of installation script to be run after" + "installation or before deinstallation"), + ('pre-install-script=', None, + "Fully qualified filename of a script to be run before " + "any files are installed. This script need not be in the " + "distribution"), + ('user-access-control=', None, + "specify Vista's UAC handling - 'none'/default=no " + "handling, 'auto'=use UAC if target Python installed for " + "all users, 'force'=always use UAC"), + ] + + boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', + 'skip-build'] + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = False + self.no_target_compile = False + self.no_target_optimize = False + self.target_version = None + self.dist_dir = None + self.bitmap = None + self.title = None + self.skip_build = False + self.install_script = None + self.pre_install_script = None + self.user_access_control = None + + + def finalize_options(self): + if self.bdist_dir is None: + if self.skip_build and self.plat_name: + # If build is skipped and plat_name is overridden, bdist will + # not see the correct 'plat_name' - so set that up manually. + bdist = self.distribution.get_command_obj('bdist') + bdist.plat_name = self.plat_name + # next the command will be initialized using that name + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'wininst') + if not self.target_version: + self.target_version = "" + if not self.skip_build and self.distribution.has_ext_modules(): + short_version = get_python_version() + if self.target_version and self.target_version != short_version: + raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \ + " option must be specified" % (short_version,)) + self.target_version = short_version + + self.set_undefined_options('bdist', 'dist_dir', 'plat_name') + + if self.install_script: + for script in self.distribution.scripts: + if self.install_script == os.path.basename(script): + break + else: + raise PackagingOptionError("install_script '%s' not found in scripts" % \ + self.install_script) + + def run(self): + if (sys.platform != "win32" and + (self.distribution.has_ext_modules() or + self.distribution.has_c_libraries())): + raise PackagingPlatformError \ + ("distribution contains extensions and/or C libraries; " + "must be compiled on a Windows 32 platform") + + if not self.skip_build: + self.run_command('build') + + install = self.get_reinitialized_command('install', + reinit_subcommands=True) + install.root = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = False + install.plat_name = self.plat_name + + install_lib = self.get_reinitialized_command('install_lib') + # we do not want to include pyc or pyo files + install_lib.compile = False + install_lib.optimize = 0 + + if self.distribution.has_ext_modules(): + # If we are building an installer for a Python version other + # than the one we are currently running, then we need to ensure + # our build_lib reflects the other Python version rather than ours. + # Note that for target_version!=sys.version, we must have skipped the + # build step, so there is no issue with enforcing the build of this + # version. + target_version = self.target_version + if not target_version: + assert self.skip_build, "Should have already checked this" + target_version = sys.version[0:3] + plat_specifier = ".%s-%s" % (self.plat_name, target_version) + build = self.get_finalized_command('build') + build.build_lib = os.path.join(build.build_base, + 'lib' + plat_specifier) + + # Use a custom scheme for the zip-file, because we have to decide + # at installation time which scheme to use. + for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): + value = key.upper() + if key == 'headers': + value = value + '/Include/$dist_name' + setattr(install, + 'install_' + key, + value) + + logger.info("installing to %s", self.bdist_dir) + install.ensure_finalized() + + # avoid warning of 'install_lib' about installing + # into a directory not in sys.path + sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) + + install.run() + + del sys.path[0] + + # And make an archive relative to the root of the + # pseudo-installation tree. + from tempfile import NamedTemporaryFile + archive_basename = NamedTemporaryFile().name + fullname = self.distribution.get_fullname() + arcname = self.make_archive(archive_basename, "zip", + root_dir=self.bdist_dir) + # create an exe containing the zip-file + self.create_exe(arcname, fullname, self.bitmap) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_wininst', pyversion, + self.get_installer_filename(fullname))) + # remove the zip-file again + logger.debug("removing temporary file '%s'", arcname) + os.remove(arcname) + + if not self.keep_temp: + if self.dry_run: + logger.info('removing %s', self.bdist_dir) + else: + rmtree(self.bdist_dir) + + def get_inidata(self): + # Return data describing the installation. + + lines = [] + metadata = self.distribution.metadata + + # Write the [metadata] section. + lines.append("[metadata]") + + # 'info' will be displayed in the installer's dialog box, + # describing the items to be installed. + info = (metadata.long_description or '') + '\n' + + # Escape newline characters + def escape(s): + return s.replace("\n", "\\n") + + for name in ["author", "author_email", "description", "maintainer", + "maintainer_email", "name", "url", "version"]: + data = getattr(metadata, name, "") + if data: + info = info + ("\n %s: %s" % \ + (name.capitalize(), escape(data))) + lines.append("%s=%s" % (name, escape(data))) + + # The [setup] section contains entries controlling + # the installer runtime. + lines.append("\n[Setup]") + if self.install_script: + lines.append("install_script=%s" % self.install_script) + lines.append("info=%s" % escape(info)) + lines.append("target_compile=%d" % (not self.no_target_compile)) + lines.append("target_optimize=%d" % (not self.no_target_optimize)) + if self.target_version: + lines.append("target_version=%s" % self.target_version) + if self.user_access_control: + lines.append("user_access_control=%s" % self.user_access_control) + + title = self.title or self.distribution.get_fullname() + lines.append("title=%s" % escape(title)) + import time + import packaging + build_info = "Built %s with packaging-%s" % \ + (time.ctime(time.time()), packaging.__version__) + lines.append("build_info=%s" % build_info) + return "\n".join(lines) + + def create_exe(self, arcname, fullname, bitmap=None): + import struct + + self.mkpath(self.dist_dir) + + cfgdata = self.get_inidata() + + installer_name = self.get_installer_filename(fullname) + logger.info("creating %s", installer_name) + + if bitmap: + with open(bitmap, "rb") as fp: + bitmapdata = fp.read() + bitmaplen = len(bitmapdata) + else: + bitmaplen = 0 + + with open(installer_name, "wb") as file: + file.write(self.get_exe_bytes()) + if bitmap: + file.write(bitmapdata) + + # Convert cfgdata from unicode to ascii, mbcs encoded + if isinstance(cfgdata, str): + cfgdata = cfgdata.encode("mbcs") + + # Append the pre-install script + cfgdata = cfgdata + "\0" + if self.pre_install_script: + with open(self.pre_install_script) as fp: + script_data = fp.read() + cfgdata = cfgdata + script_data + "\n\0" + else: + # empty pre-install script + cfgdata = cfgdata + "\0" + file.write(cfgdata) + + # The 'magic number' 0x1234567B is used to make sure that the + # binary layout of 'cfgdata' is what the wininst.exe binary + # expects. If the layout changes, increment that number, make + # the corresponding changes to the wininst.exe sources, and + # recompile them. + header = struct.pack(" cur_version: + bv = get_build_version() + else: + if self.target_version < "2.4": + bv = 6.0 + else: + bv = 7.1 + else: + # for current version - use authoritative check. + bv = get_build_version() + + # wininst-x.y.exe is in the same directory as this file + directory = os.path.dirname(__file__) + # we must use a wininst-x.y.exe built with the same C compiler + # used for python. XXX What about mingw, borland, and so on? + + # if plat_name starts with "win" but is not "win32" + # we want to strip "win" and leave the rest (e.g. -amd64) + # for all other cases, we don't want any suffix + if self.plat_name != 'win32' and self.plat_name[:3] == 'win': + sfix = self.plat_name[3:] + else: + sfix = '' + + filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix)) + with open(filename, "rb") as fp: + return fp.read() diff --git a/Lib/packaging/command/build.py b/Lib/packaging/command/build.py new file mode 100644 index 0000000000..6580fd1286 --- /dev/null +++ b/Lib/packaging/command/build.py @@ -0,0 +1,151 @@ +"""Main build command, which calls the other build_* commands.""" + +import sys +import os + +from packaging.util import get_platform +from packaging.command.cmd import Command +from packaging.errors import PackagingOptionError +from packaging.compiler import show_compilers + + +class build(Command): + + description = "build everything needed to install" + + user_options = [ + ('build-base=', 'b', + "base directory for build library"), + ('build-purelib=', None, + "build directory for platform-neutral distributions"), + ('build-platlib=', None, + "build directory for platform-specific distributions"), + ('build-lib=', None, + "build directory for all distribution (defaults to either " + + "build-purelib or build-platlib"), + ('build-scripts=', None, + "build directory for scripts"), + ('build-temp=', 't', + "temporary build directory"), + ('plat-name=', 'p', + "platform name to build for, if supported " + "(default: %s)" % get_platform()), + ('compiler=', 'c', + "specify the compiler type"), + ('debug', 'g', + "compile extensions and libraries with debugging information"), + ('force', 'f', + "forcibly build everything (ignore file timestamps)"), + ('executable=', 'e', + "specify final destination interpreter path (build.py)"), + ('use-2to3', None, + "use 2to3 to make source python 3.x compatible"), + ('convert-2to3-doctests', None, + "use 2to3 to convert doctests in seperate text files"), + ('use-2to3-fixers', None, + "list additional fixers opted for during 2to3 conversion"), + ] + + boolean_options = ['debug', 'force'] + + help_options = [ + ('help-compiler', None, + "list available compilers", show_compilers), + ] + + def initialize_options(self): + self.build_base = 'build' + # these are decided only after 'build_base' has its final value + # (unless overridden by the user or client) + self.build_purelib = None + self.build_platlib = None + self.build_lib = None + self.build_temp = None + self.build_scripts = None + self.compiler = None + self.plat_name = None + self.debug = None + self.force = False + self.executable = None + self.use_2to3 = False + self.convert_2to3_doctests = None + self.use_2to3_fixers = None + + def finalize_options(self): + if self.plat_name is None: + self.plat_name = get_platform() + else: + # plat-name only supported for windows (other platforms are + # supported via ./configure flags, if at all). Avoid misleading + # other platforms. + if os.name != 'nt': + raise PackagingOptionError( + "--plat-name only supported on Windows (try " + "using './configure --help' on your platform)") + + plat_specifier = ".%s-%s" % (self.plat_name, sys.version[0:3]) + + # Make it so Python 2.x and Python 2.x with --with-pydebug don't + # share the same build directories. Doing so confuses the build + # process for C modules + if hasattr(sys, 'gettotalrefcount'): + plat_specifier += '-pydebug' + + # 'build_purelib' and 'build_platlib' just default to 'lib' and + # 'lib.' under the base build directory. We only use one of + # them for a given distribution, though -- + if self.build_purelib is None: + self.build_purelib = os.path.join(self.build_base, 'lib') + if self.build_platlib is None: + self.build_platlib = os.path.join(self.build_base, + 'lib' + plat_specifier) + + # 'build_lib' is the actual directory that we will use for this + # particular module distribution -- if user didn't supply it, pick + # one of 'build_purelib' or 'build_platlib'. + if self.build_lib is None: + if self.distribution.ext_modules: + self.build_lib = self.build_platlib + else: + self.build_lib = self.build_purelib + + # 'build_temp' -- temporary directory for compiler turds, + # "build/temp." + if self.build_temp is None: + self.build_temp = os.path.join(self.build_base, + 'temp' + plat_specifier) + if self.build_scripts is None: + self.build_scripts = os.path.join(self.build_base, + 'scripts-' + sys.version[0:3]) + + if self.executable is None: + self.executable = os.path.normpath(sys.executable) + + def run(self): + # Run all relevant sub-commands. This will be some subset of: + # - build_py - pure Python modules + # - build_clib - standalone C libraries + # - build_ext - Python extension modules + # - build_scripts - Python scripts + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + # -- Predicates for the sub-command list --------------------------- + + def has_pure_modules(self): + return self.distribution.has_pure_modules() + + def has_c_libraries(self): + return self.distribution.has_c_libraries() + + def has_ext_modules(self): + return self.distribution.has_ext_modules() + + def has_scripts(self): + return self.distribution.has_scripts() + + sub_commands = [('build_py', has_pure_modules), + ('build_clib', has_c_libraries), + ('build_ext', has_ext_modules), + ('build_scripts', has_scripts), + ] diff --git a/Lib/packaging/command/build_clib.py b/Lib/packaging/command/build_clib.py new file mode 100644 index 0000000000..4a249964c5 --- /dev/null +++ b/Lib/packaging/command/build_clib.py @@ -0,0 +1,198 @@ +"""Build C/C++ libraries. + +This command is useful to build libraries that are included in the +distribution and needed by extension modules. +""" + +# XXX this module has *lots* of code ripped-off quite transparently from +# build_ext.py -- not surprisingly really, as the work required to build +# a static library from a collection of C source files is not really all +# that different from what's required to build a shared object file from +# a collection of C source files. Nevertheless, I haven't done the +# necessary refactoring to account for the overlap in code between the +# two modules, mainly because a number of subtle details changed in the +# cut 'n paste. Sigh. + +import os +from packaging.command.cmd import Command +from packaging.errors import PackagingSetupError +from packaging.compiler import customize_compiler +from packaging import logger + + +def show_compilers(): + from packaging.compiler import show_compilers + show_compilers() + + +class build_clib(Command): + + description = "build C/C++ libraries used by extension modules" + + user_options = [ + ('build-clib=', 'b', + "directory to build C/C++ libraries to"), + ('build-temp=', 't', + "directory to put temporary build by-products"), + ('debug', 'g', + "compile with debugging information"), + ('force', 'f', + "forcibly build everything (ignore file timestamps)"), + ('compiler=', 'c', + "specify the compiler type"), + ] + + boolean_options = ['debug', 'force'] + + help_options = [ + ('help-compiler', None, + "list available compilers", show_compilers), + ] + + def initialize_options(self): + self.build_clib = None + self.build_temp = None + + # List of libraries to build + self.libraries = None + + # Compilation options for all libraries + self.include_dirs = None + self.define = None + self.undef = None + self.debug = None + self.force = False + self.compiler = None + + + def finalize_options(self): + # This might be confusing: both build-clib and build-temp default + # to build-temp as defined by the "build" command. This is because + # I think that C libraries are really just temporary build + # by-products, at least from the point of view of building Python + # extensions -- but I want to keep my options open. + self.set_undefined_options('build', + ('build_temp', 'build_clib'), + ('build_temp', 'build_temp'), + 'compiler', 'debug', 'force') + + self.libraries = self.distribution.libraries + if self.libraries: + self.check_library_list(self.libraries) + + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + if isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + # XXX same as for build_ext -- what about 'self.define' and + # 'self.undef' ? + + def run(self): + if not self.libraries: + return + + # Yech -- this is cut 'n pasted from build_ext.py! + from packaging.compiler import new_compiler + self.compiler = new_compiler(compiler=self.compiler, + dry_run=self.dry_run, + force=self.force) + customize_compiler(self.compiler) + + if self.include_dirs is not None: + self.compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for name, value in self.define: + self.compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + self.compiler.undefine_macro(macro) + + self.build_libraries(self.libraries) + + + def check_library_list(self, libraries): + """Ensure that the list of libraries is valid. + + `library` is presumably provided as a command option 'libraries'. + This method checks that it is a list of 2-tuples, where the tuples + are (library_name, build_info_dict). + + Raise PackagingSetupError if the structure is invalid anywhere; + just returns otherwise. + """ + if not isinstance(libraries, list): + raise PackagingSetupError("'libraries' option must be a list of tuples") + + for lib in libraries: + if not isinstance(lib, tuple) and len(lib) != 2: + raise PackagingSetupError("each element of 'libraries' must a 2-tuple") + + name, build_info = lib + + if not isinstance(name, str): + raise PackagingSetupError("first element of each tuple in 'libraries' " + \ + "must be a string (the library name)") + if '/' in name or (os.sep != '/' and os.sep in name): + raise PackagingSetupError(("bad library name '%s': " + + "may not contain directory separators") % \ + lib[0]) + + if not isinstance(build_info, dict): + raise PackagingSetupError("second element of each tuple in 'libraries' " + \ + "must be a dictionary (build info)") + + def get_library_names(self): + # Assume the library list is valid -- 'check_library_list()' is + # called from 'finalize_options()', so it should be! + if not self.libraries: + return None + + lib_names = [] + for lib_name, build_info in self.libraries: + lib_names.append(lib_name) + return lib_names + + + def get_source_files(self): + self.check_library_list(self.libraries) + filenames = [] + for lib_name, build_info in self.libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise PackagingSetupError(("in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames") % lib_name) + + filenames.extend(sources) + return filenames + + def build_libraries(self, libraries): + for lib_name, build_info in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise PackagingSetupError(("in 'libraries' option (library '%s'), " + + "'sources' must be present and must be " + + "a list of source filenames") % lib_name) + sources = list(sources) + + logger.info("building '%s' library", lib_name) + + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + objects = self.compiler.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + debug=self.debug) + + # Now "link" the object files together into a static library. + # (On Unix at least, this isn't really linking -- it just + # builds an archive. Whatever.) + self.compiler.create_static_lib(objects, lib_name, + output_dir=self.build_clib, + debug=self.debug) diff --git a/Lib/packaging/command/build_ext.py b/Lib/packaging/command/build_ext.py new file mode 100644 index 0000000000..9b710411ca --- /dev/null +++ b/Lib/packaging/command/build_ext.py @@ -0,0 +1,666 @@ +"""Build extension modules.""" + +# FIXME Is this module limited to C extensions or do C++ extensions work too? +# The docstring of this module said that C++ was not supported, but other +# comments contradict that. + +import os +import re +import sys +import logging +import sysconfig + +from packaging.util import get_platform +from packaging.command.cmd import Command +from packaging.errors import (CCompilerError, CompileError, PackagingError, + PackagingPlatformError, PackagingSetupError) +from packaging.compiler import customize_compiler, show_compilers +from packaging.util import newer_group +from packaging.compiler.extension import Extension +from packaging import logger + +import site +HAS_USER_SITE = True + +if os.name == 'nt': + from packaging.compiler.msvccompiler import get_build_version + MSVC_VERSION = int(get_build_version()) + +# An extension name is just a dot-separated list of Python NAMEs (ie. +# the same as a fully-qualified module name). +extension_name_re = re.compile \ + (r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$') + + +class build_ext(Command): + + description = "build C/C++ extension modules (compile/link to build directory)" + + # XXX thoughts on how to deal with complex command-line options like + # these, i.e. how to make it so fancy_getopt can suck them off the + # command line and make it look like setup.py defined the appropriate + # lists of tuples of what-have-you. + # - each command needs a callback to process its command-line options + # - Command.__init__() needs access to its share of the whole + # command line (must ultimately come from + # Distribution.parse_command_line()) + # - it then calls the current command class' option-parsing + # callback to deal with weird options like -D, which have to + # parse the option text and churn out some custom data + # structure + # - that data structure (in this case, a list of 2-tuples) + # will then be present in the command object by the time + # we get to finalize_options() (i.e. the constructor + # takes care of both command-line and client options + # in between initialize_options() and finalize_options()) + + sep_by = " (separated by '%s')" % os.pathsep + user_options = [ + ('build-lib=', 'b', + "directory for compiled extension modules"), + ('build-temp=', 't', + "directory for temporary files (build by-products)"), + ('plat-name=', 'p', + "platform name to cross-compile for, if supported " + "(default: %s)" % get_platform()), + ('inplace', 'i', + "ignore build-lib and put compiled extensions into the source " + + "directory alongside your pure Python modules"), + ('include-dirs=', 'I', + "list of directories to search for header files" + sep_by), + ('define=', 'D', + "C preprocessor macros to define"), + ('undef=', 'U', + "C preprocessor macros to undefine"), + ('libraries=', 'l', + "external C libraries to link with"), + ('library-dirs=', 'L', + "directories to search for external C libraries" + sep_by), + ('rpath=', 'R', + "directories to search for shared C libraries at runtime"), + ('link-objects=', 'O', + "extra explicit link objects to include in the link"), + ('debug', 'g', + "compile/link with debugging information"), + ('force', 'f', + "forcibly build everything (ignore file timestamps)"), + ('compiler=', 'c', + "specify the compiler type"), + ('swig-opts=', None, + "list of SWIG command-line options"), + ('swig=', None, + "path to the SWIG executable"), + ] + + boolean_options = ['inplace', 'debug', 'force'] + + if HAS_USER_SITE: + user_options.append(('user', None, + "add user include, library and rpath")) + boolean_options.append('user') + + help_options = [ + ('help-compiler', None, + "list available compilers", show_compilers), + ] + + def initialize_options(self): + self.extensions = None + self.build_lib = None + self.plat_name = None + self.build_temp = None + self.inplace = False + self.package = None + + self.include_dirs = None + self.define = None + self.undef = None + self.libraries = None + self.library_dirs = None + self.rpath = None + self.link_objects = None + self.debug = None + self.force = None + self.compiler = None + self.swig = None + self.swig_opts = None + if HAS_USER_SITE: + self.user = None + + def finalize_options(self): + self.set_undefined_options('build', + 'build_lib', 'build_temp', 'compiler', + 'debug', 'force', 'plat_name') + + if self.package is None: + self.package = self.distribution.ext_package + + # Ensure that the list of extensions is valid, i.e. it is a list of + # Extension objects. + self.extensions = self.distribution.ext_modules + if self.extensions: + if not isinstance(self.extensions, (list, tuple)): + type_name = (self.extensions is None and 'None' + or type(self.extensions).__name__) + raise PackagingSetupError( + "'ext_modules' must be a sequence of Extension instances," + " not %s" % (type_name,)) + for i, ext in enumerate(self.extensions): + if isinstance(ext, Extension): + continue # OK! (assume type-checking done + # by Extension constructor) + type_name = (ext is None and 'None' or type(ext).__name__) + raise PackagingSetupError( + "'ext_modules' item %d must be an Extension instance," + " not %s" % (i, type_name)) + + # Make sure Python's include directories (for Python.h, pyconfig.h, + # etc.) are in the include search path. + py_include = sysconfig.get_path('include') + plat_py_include = sysconfig.get_path('platinclude') + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + if isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + # Put the Python "system" include dir at the end, so that + # any local include dirs take precedence. + self.include_dirs.append(py_include) + if plat_py_include != py_include: + self.include_dirs.append(plat_py_include) + + if isinstance(self.libraries, str): + self.libraries = [self.libraries] + + # Life is easier if we're not forever checking for None, so + # simplify these options to empty lists if unset + if self.libraries is None: + self.libraries = [] + if self.library_dirs is None: + self.library_dirs = [] + elif isinstance(self.library_dirs, str): + self.library_dirs = self.library_dirs.split(os.pathsep) + + if self.rpath is None: + self.rpath = [] + elif isinstance(self.rpath, str): + self.rpath = self.rpath.split(os.pathsep) + + # for extensions under windows use different directories + # for Release and Debug builds. + # also Python's library directory must be appended to library_dirs + if os.name == 'nt': + # the 'libs' directory is for binary installs - we assume that + # must be the *native* platform. But we don't really support + # cross-compiling via a binary install anyway, so we let it go. + self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs')) + if self.debug: + self.build_temp = os.path.join(self.build_temp, "Debug") + else: + self.build_temp = os.path.join(self.build_temp, "Release") + + # Append the source distribution include and library directories, + # this allows distutils on windows to work in the source tree + self.include_dirs.append(os.path.join(sys.exec_prefix, 'PC')) + if MSVC_VERSION == 9: + # Use the .lib files for the correct architecture + if self.plat_name == 'win32': + suffix = '' + else: + # win-amd64 or win-ia64 + suffix = self.plat_name[4:] + new_lib = os.path.join(sys.exec_prefix, 'PCbuild') + if suffix: + new_lib = os.path.join(new_lib, suffix) + self.library_dirs.append(new_lib) + + elif MSVC_VERSION == 8: + self.library_dirs.append(os.path.join(sys.exec_prefix, + 'PC', 'VS8.0')) + elif MSVC_VERSION == 7: + self.library_dirs.append(os.path.join(sys.exec_prefix, + 'PC', 'VS7.1')) + else: + self.library_dirs.append(os.path.join(sys.exec_prefix, + 'PC', 'VC6')) + + # OS/2 (EMX) doesn't support Debug vs Release builds, but has the + # import libraries in its "Config" subdirectory + if os.name == 'os2': + self.library_dirs.append(os.path.join(sys.exec_prefix, 'Config')) + + # for extensions under Cygwin and AtheOS Python's library directory must be + # appended to library_dirs + if sys.platform[:6] == 'cygwin' or sys.platform[:6] == 'atheos': + if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): + # building third party extensions + self.library_dirs.append(os.path.join(sys.prefix, "lib", + "python" + sysconfig.get_python_version(), + "config")) + else: + # building python standard extensions + self.library_dirs.append(os.curdir) + + # for extensions under Linux or Solaris with a shared Python library, + # Python's library directory must be appended to library_dirs + sysconfig.get_config_var('Py_ENABLE_SHARED') + if ((sys.platform.startswith('linux') or sys.platform.startswith('gnu') + or sys.platform.startswith('sunos')) + and sysconfig.get_config_var('Py_ENABLE_SHARED')): + if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): + # building third party extensions + self.library_dirs.append(sysconfig.get_config_var('LIBDIR')) + else: + # building python standard extensions + self.library_dirs.append(os.curdir) + + # The argument parsing will result in self.define being a string, but + # it has to be a list of 2-tuples. All the preprocessor symbols + # specified by the 'define' option will be set to '1'. Multiple + # symbols can be separated with commas. + + if self.define: + defines = self.define.split(',') + self.define = [(symbol, '1') for symbol in defines] + + # The option for macros to undefine is also a string from the + # option parsing, but has to be a list. Multiple symbols can also + # be separated with commas here. + if self.undef: + self.undef = self.undef.split(',') + + if self.swig_opts is None: + self.swig_opts = [] + else: + self.swig_opts = self.swig_opts.split(' ') + + # Finally add the user include and library directories if requested + if HAS_USER_SITE and self.user: + user_include = os.path.join(site.USER_BASE, "include") + user_lib = os.path.join(site.USER_BASE, "lib") + if os.path.isdir(user_include): + self.include_dirs.append(user_include) + if os.path.isdir(user_lib): + self.library_dirs.append(user_lib) + self.rpath.append(user_lib) + + def run(self): + from packaging.compiler import new_compiler + + # 'self.extensions', as supplied by setup.py, is a list of + # Extension instances. See the documentation for Extension (in + # distutils.extension) for details. + if not self.extensions: + return + + # If we were asked to build any C/C++ libraries, make sure that the + # directory where we put them is in the library search path for + # linking extensions. + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.libraries.extend(build_clib.get_library_names() or []) + self.library_dirs.append(build_clib.build_clib) + + # Temporary kludge until we remove the verbose arguments and use + # logging everywhere + verbose = logger.getEffectiveLevel() >= logging.DEBUG + + # Setup the CCompiler object that we'll use to do all the + # compiling and linking + self.compiler_obj = new_compiler(compiler=self.compiler, + verbose=verbose, + dry_run=self.dry_run, + force=self.force) + + customize_compiler(self.compiler_obj) + # If we are cross-compiling, init the compiler now (if we are not + # cross-compiling, init would not hurt, but people may rely on + # late initialization of compiler even if they shouldn't...) + if os.name == 'nt' and self.plat_name != get_platform(): + self.compiler_obj.initialize(self.plat_name) + + # And make sure that any compile/link-related options (which might + # come from the command line or from the setup script) are set in + # that CCompiler object -- that way, they automatically apply to + # all compiling and linking done here. + if self.include_dirs is not None: + self.compiler_obj.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for name, value in self.define: + self.compiler_obj.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + self.compiler_obj.undefine_macro(macro) + if self.libraries is not None: + self.compiler_obj.set_libraries(self.libraries) + if self.library_dirs is not None: + self.compiler_obj.set_library_dirs(self.library_dirs) + if self.rpath is not None: + self.compiler_obj.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + self.compiler_obj.set_link_objects(self.link_objects) + + # Now actually compile and link everything. + self.build_extensions() + + def get_source_files(self): + filenames = [] + + # Wouldn't it be neat if we knew the names of header files too... + for ext in self.extensions: + filenames.extend(ext.sources) + + return filenames + + def get_outputs(self): + # And build the list of output (built) filenames. Note that this + # ignores the 'inplace' flag, and assumes everything goes in the + # "build" tree. + outputs = [] + for ext in self.extensions: + outputs.append(self.get_ext_fullpath(ext.name)) + return outputs + + def build_extensions(self): + for ext in self.extensions: + try: + self.build_extension(ext) + except (CCompilerError, PackagingError, CompileError) as e: + if not ext.optional: + raise + logger.warning('%s: building extension %r failed: %s', + self.get_command_name(), ext.name, e) + + def build_extension(self, ext): + sources = ext.sources + if sources is None or not isinstance(sources, (list, tuple)): + raise PackagingSetupError(("in 'ext_modules' option (extension '%s'), " + + "'sources' must be present and must be " + + "a list of source filenames") % ext.name) + sources = list(sources) + + ext_path = self.get_ext_fullpath(ext.name) + depends = sources + ext.depends + if not (self.force or newer_group(depends, ext_path, 'newer')): + logger.debug("skipping '%s' extension (up-to-date)", ext.name) + return + else: + logger.info("building '%s' extension", ext.name) + + # First, scan the sources for SWIG definition files (.i), run + # SWIG on 'em to create .c files, and modify the sources list + # accordingly. + sources = self.swig_sources(sources, ext) + + # Next, compile the source code to object files. + + # XXX not honouring 'define_macros' or 'undef_macros' -- the + # CCompiler API needs to change to accommodate this, and I + # want to do one thing at a time! + + # Two possible sources for extra compiler arguments: + # - 'extra_compile_args' in Extension object + # - CFLAGS environment variable (not particularly + # elegant, but people seem to expect it and I + # guess it's useful) + # The environment variable should take precedence, and + # any sensible compiler will give precedence to later + # command-line args. Hence we combine them in order: + extra_args = ext.extra_compile_args or [] + + macros = ext.define_macros[:] + for undef in ext.undef_macros: + macros.append((undef,)) + + objects = self.compiler_obj.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=ext.include_dirs, + debug=self.debug, + extra_postargs=extra_args, + depends=ext.depends) + + # XXX -- this is a Vile HACK! + # + # The setup.py script for Python on Unix needs to be able to + # get this list so it can perform all the clean up needed to + # avoid keeping object files around when cleaning out a failed + # build of an extension module. Since Packaging does not + # track dependencies, we have to get rid of intermediates to + # ensure all the intermediates will be properly re-built. + # + self._built_objects = objects[:] + + # Now link the object files together into a "shared object" -- + # of course, first we have to figure out all the other things + # that go into the mix. + if ext.extra_objects: + objects.extend(ext.extra_objects) + extra_args = ext.extra_link_args or [] + + # Detect target language, if not provided + language = ext.language or self.compiler_obj.detect_language(sources) + + self.compiler_obj.link_shared_object( + objects, ext_path, + libraries=self.get_libraries(ext), + library_dirs=ext.library_dirs, + runtime_library_dirs=ext.runtime_library_dirs, + extra_postargs=extra_args, + export_symbols=self.get_export_symbols(ext), + debug=self.debug, + build_temp=self.build_temp, + target_lang=language) + + + def swig_sources(self, sources, extension): + """Walk the list of source files in 'sources', looking for SWIG + interface (.i) files. Run SWIG on all that are found, and + return a modified 'sources' list with SWIG source files replaced + by the generated C (or C++) files. + """ + new_sources = [] + swig_sources = [] + swig_targets = {} + + # XXX this drops generated C/C++ files into the source tree, which + # is fine for developers who want to distribute the generated + # source -- but there should be an option to put SWIG output in + # the temp dir. + + if ('-c++' in self.swig_opts or '-c++' in extension.swig_opts): + target_ext = '.cpp' + else: + target_ext = '.c' + + for source in sources: + base, ext = os.path.splitext(source) + if ext == ".i": # SWIG interface file + new_sources.append(base + '_wrap' + target_ext) + swig_sources.append(source) + swig_targets[source] = new_sources[-1] + else: + new_sources.append(source) + + if not swig_sources: + return new_sources + + swig = self.swig or self.find_swig() + swig_cmd = [swig, "-python"] + swig_cmd.extend(self.swig_opts) + + # Do not override commandline arguments + if not self.swig_opts: + for o in extension.swig_opts: + swig_cmd.append(o) + + for source in swig_sources: + target = swig_targets[source] + logger.info("swigging %s to %s", source, target) + self.spawn(swig_cmd + ["-o", target, source]) + + return new_sources + + def find_swig(self): + """Return the name of the SWIG executable. On Unix, this is + just "swig" -- it should be in the PATH. Tries a bit harder on + Windows. + """ + + if os.name == "posix": + return "swig" + elif os.name == "nt": + + # Look for SWIG in its standard installation directory on + # Windows (or so I presume!). If we find it there, great; + # if not, act like Unix and assume it's in the PATH. + for vers in ("1.3", "1.2", "1.1"): + fn = os.path.join("c:\\swig%s" % vers, "swig.exe") + if os.path.isfile(fn): + return fn + else: + return "swig.exe" + + elif os.name == "os2": + # assume swig available in the PATH. + return "swig.exe" + + else: + raise PackagingPlatformError(("I don't know how to find (much less run) SWIG " + "on platform '%s'") % os.name) + + # -- Name generators ----------------------------------------------- + # (extension names, filenames, whatever) + def get_ext_fullpath(self, ext_name): + """Returns the path of the filename for a given extension. + + The file is located in `build_lib` or directly in the package + (inplace option). + """ + fullname = self.get_ext_fullname(ext_name) + modpath = fullname.split('.') + filename = self.get_ext_filename(modpath[-1]) + + if not self.inplace: + # no further work needed + # returning : + # build_dir/package/path/filename + filename = os.path.join(*modpath[:-1]+[filename]) + return os.path.join(self.build_lib, filename) + + # the inplace option requires to find the package directory + # using the build_py command for that + package = '.'.join(modpath[0:-1]) + build_py = self.get_finalized_command('build_py') + package_dir = os.path.abspath(build_py.get_package_dir(package)) + + # returning + # package_dir/filename + return os.path.join(package_dir, filename) + + def get_ext_fullname(self, ext_name): + """Returns the fullname of a given extension name. + + Adds the `package.` prefix""" + if self.package is None: + return ext_name + else: + return self.package + '.' + ext_name + + def get_ext_filename(self, ext_name): + r"""Convert the name of an extension (eg. "foo.bar") into the name + of the file from which it will be loaded (eg. "foo/bar.so", or + "foo\bar.pyd"). + """ + ext_path = ext_name.split('.') + # OS/2 has an 8 character module (extension) limit :-( + if os.name == "os2": + ext_path[len(ext_path) - 1] = ext_path[len(ext_path) - 1][:8] + # extensions in debug_mode are named 'module_d.pyd' under windows + so_ext = sysconfig.get_config_var('SO') + if os.name == 'nt' and self.debug: + return os.path.join(*ext_path) + '_d' + so_ext + return os.path.join(*ext_path) + so_ext + + def get_export_symbols(self, ext): + """Return the list of symbols that a shared extension has to + export. This either uses 'ext.export_symbols' or, if it's not + provided, "init" + module_name. Only relevant on Windows, where + the .pyd file (DLL) must export the module "init" function. + """ + initfunc_name = "init" + ext.name.split('.')[-1] + if initfunc_name not in ext.export_symbols: + ext.export_symbols.append(initfunc_name) + return ext.export_symbols + + def get_libraries(self, ext): + """Return the list of libraries to link against when building a + shared extension. On most platforms, this is just 'ext.libraries'; + on Windows and OS/2, we add the Python library (eg. python20.dll). + """ + # The python library is always needed on Windows. For MSVC, this + # is redundant, since the library is mentioned in a pragma in + # pyconfig.h that MSVC groks. The other Windows compilers all seem + # to need it mentioned explicitly, though, so that's what we do. + # Append '_d' to the python import library on debug builds. + if sys.platform == "win32": + from packaging.compiler.msvccompiler import MSVCCompiler + if not isinstance(self.compiler_obj, MSVCCompiler): + template = "python%d%d" + if self.debug: + template = template + '_d' + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + # don't extend ext.libraries, it may be shared with other + # extensions, it is a reference to the original list + return ext.libraries + [pythonlib] + else: + return ext.libraries + elif sys.platform == "os2emx": + # EMX/GCC requires the python library explicitly, and I + # believe VACPP does as well (though not confirmed) - AIM Apr01 + template = "python%d%d" + # debug versions of the main DLL aren't supported, at least + # not at this time - AIM Apr01 + #if self.debug: + # template = template + '_d' + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + # don't extend ext.libraries, it may be shared with other + # extensions, it is a reference to the original list + return ext.libraries + [pythonlib] + elif sys.platform[:6] == "cygwin": + template = "python%d.%d" + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + # don't extend ext.libraries, it may be shared with other + # extensions, it is a reference to the original list + return ext.libraries + [pythonlib] + elif sys.platform[:6] == "atheos": + template = "python%d.%d" + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + # Get SHLIBS from Makefile + extra = [] + for lib in sysconfig.get_config_var('SHLIBS').split(): + if lib.startswith('-l'): + extra.append(lib[2:]) + else: + extra.append(lib) + # don't extend ext.libraries, it may be shared with other + # extensions, it is a reference to the original list + return ext.libraries + [pythonlib, "m"] + extra + + elif sys.platform == 'darwin': + # Don't use the default code below + return ext.libraries + + else: + if sysconfig.get_config_var('Py_ENABLE_SHARED'): + template = "python%d.%d" + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + return ext.libraries + [pythonlib] + else: + return ext.libraries diff --git a/Lib/packaging/command/build_py.py b/Lib/packaging/command/build_py.py new file mode 100644 index 0000000000..360f4c96ad --- /dev/null +++ b/Lib/packaging/command/build_py.py @@ -0,0 +1,410 @@ +"""Build pure Python modules (just copy to build directory).""" + +import os +import sys +from glob import glob + +from packaging import logger +from packaging.command.cmd import Command +from packaging.errors import PackagingOptionError, PackagingFileError +from packaging.util import convert_path +from packaging.compat import Mixin2to3 + +# marking public APIs +__all__ = ['build_py'] + +class build_py(Command, Mixin2to3): + + description = "build pure Python modules (copy to build directory)" + + user_options = [ + ('build-lib=', 'd', "directory to build (copy) to"), + ('compile', 'c', "compile .py to .pyc"), + ('no-compile', None, "don't compile .py files [default]"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('force', 'f', "forcibly build everything (ignore file timestamps)"), + ('use-2to3', None, + "use 2to3 to make source python 3.x compatible"), + ('convert-2to3-doctests', None, + "use 2to3 to convert doctests in seperate text files"), + ('use-2to3-fixers', None, + "list additional fixers opted for during 2to3 conversion"), + ] + + boolean_options = ['compile', 'force'] + negative_opt = {'no-compile' : 'compile'} + + def initialize_options(self): + self.build_lib = None + self.py_modules = None + self.package = None + self.package_data = None + self.package_dir = None + self.compile = False + self.optimize = 0 + self.force = None + self._updated_files = [] + self._doctests_2to3 = [] + self.use_2to3 = False + self.convert_2to3_doctests = None + self.use_2to3_fixers = None + + def finalize_options(self): + self.set_undefined_options('build', + 'use_2to3', 'use_2to3_fixers', + 'convert_2to3_doctests', 'build_lib', + 'force') + + # Get the distribution options that are aliases for build_py + # options -- list of packages and list of modules. + self.packages = self.distribution.packages + self.py_modules = self.distribution.py_modules + self.package_data = self.distribution.package_data + self.package_dir = None + if self.distribution.package_dir is not None: + self.package_dir = convert_path(self.distribution.package_dir) + self.data_files = self.get_data_files() + + # Ick, copied straight from install_lib.py (fancy_getopt needs a + # type system! Hell, *everything* needs a type system!!!) + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + assert 0 <= self.optimize <= 2 + except (ValueError, AssertionError): + raise PackagingOptionError("optimize must be 0, 1, or 2") + + def run(self): + # XXX copy_file by default preserves atime and mtime. IMHO this is + # the right thing to do, but perhaps it should be an option -- in + # particular, a site administrator might want installed files to + # reflect the time of installation rather than the last + # modification time before the installed release. + + # XXX copy_file by default preserves mode, which appears to be the + # wrong thing to do: if a file is read-only in the working + # directory, we want it to be installed read/write so that the next + # installation of the same module distribution can overwrite it + # without problems. (This might be a Unix-specific issue.) Thus + # we turn off 'preserve_mode' when copying to the build directory, + # since the build directory is supposed to be exactly what the + # installation will look like (ie. we preserve mode when + # installing). + + # Two options control which modules will be installed: 'packages' + # and 'py_modules'. The former lets us work with whole packages, not + # specifying individual modules at all; the latter is for + # specifying modules one-at-a-time. + + if self.py_modules: + self.build_modules() + if self.packages: + self.build_packages() + self.build_package_data() + + if self.use_2to3 and self._updated_files: + self.run_2to3(self._updated_files, self._doctests_2to3, + self.use_2to3_fixers) + + self.byte_compile(self.get_outputs(include_bytecode=False)) + + # -- Top-level worker functions ------------------------------------ + + def get_data_files(self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples. + + Helper function for `finalize_options()`. + """ + data = [] + if not self.packages: + return data + for package in self.packages: + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Length of path to strip from found files + plen = 0 + if src_dir: + plen = len(src_dir)+1 + + # Strip directory from globbed filenames + filenames = [ + file[plen:] for file in self.find_data_files(package, src_dir) + ] + data.append((package, src_dir, build_dir, filenames)) + return data + + def find_data_files(self, package, src_dir): + """Return filenames for package's data files in 'src_dir'. + + Helper function for `get_data_files()`. + """ + globs = (self.package_data.get('', []) + + self.package_data.get(package, [])) + files = [] + for pattern in globs: + # Each pattern has to be converted to a platform-specific path + filelist = glob(os.path.join(src_dir, convert_path(pattern))) + # Files that match more than one pattern are only added once + files.extend(fn for fn in filelist if fn not in files) + return files + + def build_package_data(self): + """Copy data files into build directory. + + Helper function for `run()`. + """ + # FIXME add tests for this method + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + srcfile = os.path.join(src_dir, filename) + self.mkpath(os.path.dirname(target)) + outf, copied = self.copy_file(srcfile, + target, preserve_mode=False) + if copied and srcfile in self.distribution.convert_2to3.doctests: + self._doctests_2to3.append(outf) + + # XXX - this should be moved to the Distribution class as it is not + # only needed for build_py. It also has no dependencies on this class. + def get_package_dir(self, package): + """Return the directory, relative to the top of the source + distribution, where package 'package' should be found + (at least according to the 'package_dir' option, if any).""" + + path = package.split('.') + if self.package_dir is not None: + path.insert(0, self.package_dir) + + if len(path) > 0: + return os.path.join(*path) + + return '' + + def check_package(self, package, package_dir): + """Helper function for `find_package_modules()` and `find_modules()'. + """ + # Empty dir name means current directory, which we can probably + # assume exists. Also, os.path.exists and isdir don't know about + # my "empty string means current dir" convention, so we have to + # circumvent them. + if package_dir != "": + if not os.path.exists(package_dir): + raise PackagingFileError( + "package directory '%s' does not exist" % package_dir) + if not os.path.isdir(package_dir): + raise PackagingFileError( + "supposed package directory '%s' exists, " + "but is not a directory" % package_dir) + + # Require __init__.py for all but the "root package" + if package: + init_py = os.path.join(package_dir, "__init__.py") + if os.path.isfile(init_py): + return init_py + else: + logger.warning(("package init file '%s' not found " + + "(or not a regular file)"), init_py) + + # Either not in a package at all (__init__.py not expected), or + # __init__.py doesn't exist -- so don't return the filename. + return None + + def check_module(self, module, module_file): + if not os.path.isfile(module_file): + logger.warning("file %s (for module %s) not found", + module_file, module) + return False + else: + return True + + def find_package_modules(self, package, package_dir): + self.check_package(package, package_dir) + module_files = glob(os.path.join(package_dir, "*.py")) + modules = [] + if self.distribution.script_name is not None: + setup_script = os.path.abspath(self.distribution.script_name) + else: + setup_script = None + + for f in module_files: + abs_f = os.path.abspath(f) + if abs_f != setup_script: + module = os.path.splitext(os.path.basename(f))[0] + modules.append((package, module, f)) + else: + logger.debug("excluding %s", setup_script) + return modules + + def find_modules(self): + """Finds individually-specified Python modules, ie. those listed by + module name in 'self.py_modules'. Returns a list of tuples (package, + module_base, filename): 'package' is a tuple of the path through + package-space to the module; 'module_base' is the bare (no + packages, no dots) module name, and 'filename' is the path to the + ".py" file (relative to the distribution root) that implements the + module. + """ + # Map package names to tuples of useful info about the package: + # (package_dir, checked) + # package_dir - the directory where we'll find source files for + # this package + # checked - true if we have checked that the package directory + # is valid (exists, contains __init__.py, ... ?) + packages = {} + + # List of (package, module, filename) tuples to return + modules = [] + + # We treat modules-in-packages almost the same as toplevel modules, + # just the "package" for a toplevel is empty (either an empty + # string or empty list, depending on context). Differences: + # - don't check for __init__.py in directory for empty package + for module in self.py_modules: + path = module.split('.') + package = '.'.join(path[0:-1]) + module_base = path[-1] + + try: + package_dir, checked = packages[package] + except KeyError: + package_dir = self.get_package_dir(package) + checked = False + + if not checked: + init_py = self.check_package(package, package_dir) + packages[package] = (package_dir, 1) + if init_py: + modules.append((package, "__init__", init_py)) + + # XXX perhaps we should also check for just .pyc files + # (so greedy closed-source bastards can distribute Python + # modules too) + module_file = os.path.join(package_dir, module_base + ".py") + if not self.check_module(module, module_file): + continue + + modules.append((package, module_base, module_file)) + + return modules + + def find_all_modules(self): + """Compute the list of all modules that will be built, whether + they are specified one-module-at-a-time ('self.py_modules') or + by whole packages ('self.packages'). Return a list of tuples + (package, module, module_file), just like 'find_modules()' and + 'find_package_modules()' do.""" + modules = [] + if self.py_modules: + modules.extend(self.find_modules()) + if self.packages: + for package in self.packages: + package_dir = self.get_package_dir(package) + m = self.find_package_modules(package, package_dir) + modules.extend(m) + return modules + + def get_source_files(self): + sources = [module[-1] for module in self.find_all_modules()] + sources += [ + os.path.join(src_dir, filename) + for package, src_dir, build_dir, filenames in self.data_files + for filename in filenames] + return sources + + def get_module_outfile(self, build_dir, package, module): + outfile_path = [build_dir] + list(package) + [module + ".py"] + return os.path.join(*outfile_path) + + def get_outputs(self, include_bytecode=True): + modules = self.find_all_modules() + outputs = [] + for package, module, module_file in modules: + package = package.split('.') + filename = self.get_module_outfile(self.build_lib, package, module) + outputs.append(filename) + if include_bytecode: + if self.compile: + outputs.append(filename + "c") + if self.optimize > 0: + outputs.append(filename + "o") + + outputs += [ + os.path.join(build_dir, filename) + for package, src_dir, build_dir, filenames in self.data_files + for filename in filenames] + + return outputs + + def build_module(self, module, module_file, package): + if isinstance(package, str): + package = package.split('.') + elif not isinstance(package, (list, tuple)): + raise TypeError( + "'package' must be a string (dot-separated), list, or tuple") + + # Now put the module source file into the "build" area -- this is + # easy, we just copy it somewhere under self.build_lib (the build + # directory for Python source). + outfile = self.get_module_outfile(self.build_lib, package, module) + dir = os.path.dirname(outfile) + self.mkpath(dir) + return self.copy_file(module_file, outfile, preserve_mode=False) + + def build_modules(self): + modules = self.find_modules() + for package, module, module_file in modules: + + # Now "build" the module -- ie. copy the source file to + # self.build_lib (the build directory for Python source). + # (Actually, it gets copied to the directory for this package + # under self.build_lib.) + self.build_module(module, module_file, package) + + def build_packages(self): + for package in self.packages: + + # Get list of (package, module, module_file) tuples based on + # scanning the package directory. 'package' is only included + # in the tuple so that 'find_modules()' and + # 'find_package_tuples()' have a consistent interface; it's + # ignored here (apart from a sanity check). Also, 'module' is + # the *unqualified* module name (ie. no dots, no package -- we + # already know its package!), and 'module_file' is the path to + # the .py file, relative to the current directory + # (ie. including 'package_dir'). + package_dir = self.get_package_dir(package) + modules = self.find_package_modules(package, package_dir) + + # Now loop over the modules we found, "building" each one (just + # copy it to self.build_lib). + for package_, module, module_file in modules: + assert package == package_ + self.build_module(module, module_file, package) + + def byte_compile(self, files): + if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode: + logger.warning('%s: byte-compiling is disabled, skipping.', + self.get_command_name()) + return + + from packaging.util import byte_compile + prefix = self.build_lib + if prefix[-1] != os.sep: + prefix = prefix + os.sep + + # XXX this code is essentially the same as the 'byte_compile() + # method of the "install_lib" command, except for the determination + # of the 'prefix' string. Hmmm. + + if self.compile: + byte_compile(files, optimize=0, + force=self.force, prefix=prefix, dry_run=self.dry_run) + if self.optimize > 0: + byte_compile(files, optimize=self.optimize, + force=self.force, prefix=prefix, dry_run=self.dry_run) diff --git a/Lib/packaging/command/build_scripts.py b/Lib/packaging/command/build_scripts.py new file mode 100644 index 0000000000..7fba0e51cd --- /dev/null +++ b/Lib/packaging/command/build_scripts.py @@ -0,0 +1,132 @@ +"""Build scripts (copy to build dir and fix up shebang line).""" + +import os +import re +import sysconfig + +from packaging.command.cmd import Command +from packaging.util import convert_path, newer +from packaging import logger +from packaging.compat import Mixin2to3 + + +# check if Python is called on the first line with this expression +first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$') + +class build_scripts(Command, Mixin2to3): + + description = "build scripts (copy and fix up shebang line)" + + user_options = [ + ('build-dir=', 'd', "directory to build (copy) to"), + ('force', 'f', "forcibly build everything (ignore file timestamps"), + ('executable=', 'e', "specify final destination interpreter path"), + ] + + boolean_options = ['force'] + + + def initialize_options(self): + self.build_dir = None + self.scripts = None + self.force = None + self.executable = None + self.outfiles = None + self.use_2to3 = False + self.convert_2to3_doctests = None + self.use_2to3_fixers = None + + def finalize_options(self): + self.set_undefined_options('build', + ('build_scripts', 'build_dir'), + 'use_2to3', 'use_2to3_fixers', + 'convert_2to3_doctests', 'force', + 'executable') + self.scripts = self.distribution.scripts + + def get_source_files(self): + return self.scripts + + def run(self): + if not self.scripts: + return + copied_files = self.copy_scripts() + if self.use_2to3 and copied_files: + self._run_2to3(copied_files, fixers=self.use_2to3_fixers) + + def copy_scripts(self): + """Copy each script listed in 'self.scripts'; if it's marked as a + Python script in the Unix way (first line matches 'first_line_re', + ie. starts with "\#!" and contains "python"), then adjust the first + line to refer to the current Python interpreter as we copy. + """ + self.mkpath(self.build_dir) + outfiles = [] + for script in self.scripts: + adjust = False + script = convert_path(script) + outfile = os.path.join(self.build_dir, os.path.basename(script)) + outfiles.append(outfile) + + if not self.force and not newer(script, outfile): + logger.debug("not copying %s (up-to-date)", script) + continue + + # Always open the file, but ignore failures in dry-run mode -- + # that way, we'll get accurate feedback if we can read the + # script. + try: + f = open(script, "r") + except IOError: + if not self.dry_run: + raise + f = None + else: + first_line = f.readline() + if not first_line: + logger.warning('%s: %s is an empty file (skipping)', + self.get_command_name(), script) + continue + + match = first_line_re.match(first_line) + if match: + adjust = True + post_interp = match.group(1) or '' + + if adjust: + logger.info("copying and adjusting %s -> %s", script, + self.build_dir) + if not self.dry_run: + outf = open(outfile, "w") + if not sysconfig.is_python_build(): + outf.write("#!%s%s\n" % + (self.executable, + post_interp)) + else: + outf.write("#!%s%s\n" % + (os.path.join( + sysconfig.get_config_var("BINDIR"), + "python%s%s" % (sysconfig.get_config_var("VERSION"), + sysconfig.get_config_var("EXE"))), + post_interp)) + outf.writelines(f.readlines()) + outf.close() + if f: + f.close() + else: + if f: + f.close() + self.copy_file(script, outfile) + + if os.name == 'posix': + for file in outfiles: + if self.dry_run: + logger.info("changing mode of %s", file) + else: + oldmode = os.stat(file).st_mode & 0o7777 + newmode = (oldmode | 0o555) & 0o7777 + if newmode != oldmode: + logger.info("changing mode of %s from %o to %o", + file, oldmode, newmode) + os.chmod(file, newmode) + return outfiles diff --git a/Lib/packaging/command/check.py b/Lib/packaging/command/check.py new file mode 100644 index 0000000000..94c4a97c15 --- /dev/null +++ b/Lib/packaging/command/check.py @@ -0,0 +1,88 @@ +"""Check PEP compliance of metadata.""" + +from packaging import logger +from packaging.command.cmd import Command +from packaging.errors import PackagingSetupError +from packaging.util import resolve_name + +class check(Command): + + description = "check PEP compliance of metadata" + + user_options = [('metadata', 'm', 'Verify metadata'), + ('all', 'a', + ('runs extended set of checks')), + ('strict', 's', + 'Will exit with an error if a check fails')] + + boolean_options = ['metadata', 'all', 'strict'] + + def initialize_options(self): + """Sets default values for options.""" + self.all = False + self.metadata = True + self.strict = False + self._warnings = [] + + def finalize_options(self): + pass + + def warn(self, msg, *args): + """Wrapper around logging that also remembers messages.""" + # XXX we could use a special handler for this, but would need to test + # if it works even if the logger has a too high level + self._warnings.append((msg, args)) + return logger.warning(self.get_command_name() + msg, *args) + + def run(self): + """Runs the command.""" + # perform the various tests + if self.metadata: + self.check_metadata() + if self.all: + self.check_restructuredtext() + self.check_hooks_resolvable() + + # let's raise an error in strict mode, if we have at least + # one warning + if self.strict and len(self._warnings) > 0: + msg = '\n'.join(msg % args for msg, args in self._warnings) + raise PackagingSetupError(msg) + + def check_metadata(self): + """Ensures that all required elements of metadata are supplied. + + name, version, URL, author + + Warns if any are missing. + """ + missing, warnings = self.distribution.metadata.check(strict=True) + if missing != []: + self.warn('missing required metadata: %s', ', '.join(missing)) + for warning in warnings: + self.warn(warning) + + def check_restructuredtext(self): + """Checks if the long string fields are reST-compliant.""" + missing, warnings = self.distribution.metadata.check(restructuredtext=True) + if self.distribution.metadata.docutils_support: + for warning in warnings: + line = warning[-1].get('line') + if line is None: + warning = warning[1] + else: + warning = '%s (line %s)' % (warning[1], line) + self.warn(warning) + elif self.strict: + raise PackagingSetupError('The docutils package is needed.') + + def check_hooks_resolvable(self): + for options in self.distribution.command_options.values(): + for hook_kind in ("pre_hook", "post_hook"): + if hook_kind not in options: + break + for hook_name in options[hook_kind][1].values(): + try: + resolve_name(hook_name) + except ImportError: + self.warn('name %r cannot be resolved', hook_name) diff --git a/Lib/packaging/command/clean.py b/Lib/packaging/command/clean.py new file mode 100644 index 0000000000..4f60f4ea93 --- /dev/null +++ b/Lib/packaging/command/clean.py @@ -0,0 +1,76 @@ +"""Clean up temporary files created by the build command.""" + +# Contributed by Bastian Kleineidam + +import os +from shutil import rmtree +from packaging.command.cmd import Command +from packaging import logger + +class clean(Command): + + description = "clean up temporary files from 'build' command" + user_options = [ + ('build-base=', 'b', + "base build directory (default: 'build.build-base')"), + ('build-lib=', None, + "build directory for all modules (default: 'build.build-lib')"), + ('build-temp=', 't', + "temporary build directory (default: 'build.build-temp')"), + ('build-scripts=', None, + "build directory for scripts (default: 'build.build-scripts')"), + ('bdist-base=', None, + "temporary directory for built distributions"), + ('all', 'a', + "remove all build output, not just temporary by-products") + ] + + boolean_options = ['all'] + + def initialize_options(self): + self.build_base = None + self.build_lib = None + self.build_temp = None + self.build_scripts = None + self.bdist_base = None + self.all = None + + def finalize_options(self): + self.set_undefined_options('build', 'build_base', 'build_lib', + 'build_scripts', 'build_temp') + self.set_undefined_options('bdist', 'bdist_base') + + def run(self): + # remove the build/temp. directory (unless it's already + # gone) + if os.path.exists(self.build_temp): + if self.dry_run: + logger.info('removing %s', self.build_temp) + else: + rmtree(self.build_temp) + else: + logger.debug("'%s' does not exist -- can't clean it", + self.build_temp) + + if self.all: + # remove build directories + for directory in (self.build_lib, + self.bdist_base, + self.build_scripts): + if os.path.exists(directory): + if self.dry_run: + logger.info('removing %s', directory) + else: + rmtree(directory) + else: + logger.warning("'%s' does not exist -- can't clean it", + directory) + + # just for the heck of it, try to remove the base build directory: + # we might have emptied it right now, but if not we don't care + if not self.dry_run: + try: + os.rmdir(self.build_base) + logger.info("removing '%s'", self.build_base) + except OSError: + pass diff --git a/Lib/packaging/command/cmd.py b/Lib/packaging/command/cmd.py new file mode 100644 index 0000000000..fa56aa63f6 --- /dev/null +++ b/Lib/packaging/command/cmd.py @@ -0,0 +1,440 @@ +"""Base class for commands.""" + +import os +import re +from shutil import copyfile, move, make_archive +from packaging import util +from packaging import logger +from packaging.errors import PackagingOptionError + + +class Command: + """Abstract base class for defining command classes, the "worker bees" + of the Packaging. A useful analogy for command classes is to think of + them as subroutines with local variables called "options". The options + are "declared" in 'initialize_options()' and "defined" (given their + final values, aka "finalized") in 'finalize_options()', both of which + must be defined by every command class. The distinction between the + two is necessary because option values might come from the outside + world (command line, config file, ...), and any options dependent on + other options must be computed *after* these outside influences have + been processed -- hence 'finalize_options()'. The "body" of the + subroutine, where it does all its work based on the values of its + options, is the 'run()' method, which must also be implemented by every + command class. + """ + + # 'sub_commands' formalizes the notion of a "family" of commands, + # eg. "install_dist" as the parent with sub-commands "install_lib", + # "install_headers", etc. The parent of a family of commands + # defines 'sub_commands' as a class attribute; it's a list of + # (command_name : string, predicate : unbound_method | string | None) + # tuples, where 'predicate' is a method of the parent command that + # determines whether the corresponding command is applicable in the + # current situation. (Eg. we "install_headers" is only applicable if + # we have any C header files to install.) If 'predicate' is None, + # that command is always applicable. + # + # 'sub_commands' is usually defined at the *end* of a class, because + # predicates can be unbound methods, so they must already have been + # defined. The canonical example is the "install_dist" command. + sub_commands = [] + + # Pre and post command hooks are run just before or just after the command + # itself. They are simple functions that receive the command instance. They + # are specified as callable objects or dotted strings (for lazy loading). + pre_hook = None + post_hook = None + + # -- Creation/initialization methods ------------------------------- + + def __init__(self, dist): + """Create and initialize a new Command object. Most importantly, + invokes the 'initialize_options()' method, which is the real + initializer and depends on the actual command being instantiated. + """ + # late import because of mutual dependence between these classes + from packaging.dist import Distribution + + if not isinstance(dist, Distribution): + raise TypeError("dist must be a Distribution instance") + if self.__class__ is Command: + raise RuntimeError("Command is an abstract class") + + self.distribution = dist + self.initialize_options() + + # Per-command versions of the global flags, so that the user can + # customize Packaging' behaviour command-by-command and let some + # commands fall back on the Distribution's behaviour. None means + # "not defined, check self.distribution's copy", while 0 or 1 mean + # false and true (duh). Note that this means figuring out the real + # value of each flag is a touch complicated -- hence "self._dry_run" + # will be handled by a property, below. + # XXX This needs to be fixed. [I changed it to a property--does that + # "fix" it?] + self._dry_run = None + + # Some commands define a 'self.force' option to ignore file + # timestamps, but methods defined *here* assume that + # 'self.force' exists for all commands. So define it here + # just to be safe. + self.force = None + + # The 'help' flag is just used for command line parsing, so + # none of that complicated bureaucracy is needed. + self.help = False + + # 'finalized' records whether or not 'finalize_options()' has been + # called. 'finalize_options()' itself should not pay attention to + # this flag: it is the business of 'ensure_finalized()', which + # always calls 'finalize_options()', to respect/update it. + self.finalized = False + + # XXX A more explicit way to customize dry_run would be better. + @property + def dry_run(self): + if self._dry_run is None: + return getattr(self.distribution, 'dry_run') + else: + return self._dry_run + + def ensure_finalized(self): + if not self.finalized: + self.finalize_options() + self.finalized = True + + # Subclasses must define: + # initialize_options() + # provide default values for all options; may be customized by + # setup script, by options from config file(s), or by command-line + # options + # finalize_options() + # decide on the final values for all options; this is called + # after all possible intervention from the outside world + # (command line, option file, etc.) has been processed + # run() + # run the command: do whatever it is we're here to do, + # controlled by the command's various option values + + def initialize_options(self): + """Set default values for all the options that this command + supports. Note that these defaults may be overridden by other + commands, by the setup script, by config files, or by the + command line. Thus, this is not the place to code dependencies + between options; generally, 'initialize_options()' implementations + are just a bunch of "self.foo = None" assignments. + + This method must be implemented by all command classes. + """ + raise RuntimeError( + "abstract method -- subclass %s must override" % self.__class__) + + def finalize_options(self): + """Set final values for all the options that this command supports. + This is always called as late as possible, ie. after any option + assignments from the command line or from other commands have been + done. Thus, this is the place to code option dependencies: if + 'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as + long as 'foo' still has the same value it was assigned in + 'initialize_options()'. + + This method must be implemented by all command classes. + """ + raise RuntimeError( + "abstract method -- subclass %s must override" % self.__class__) + + def dump_options(self, header=None, indent=""): + if header is None: + header = "command options for '%s':" % self.get_command_name() + logger.info(indent + header) + indent = indent + " " + negative_opt = getattr(self, 'negative_opt', ()) + for option, _, _ in self.user_options: + if option in negative_opt: + continue + option = option.replace('-', '_') + if option[-1] == "=": + option = option[:-1] + value = getattr(self, option) + logger.info(indent + "%s = %s", option, value) + + def run(self): + """A command's raison d'etre: carry out the action it exists to + perform, controlled by the options initialized in + 'initialize_options()', customized by other commands, the setup + script, the command line and config files, and finalized in + 'finalize_options()'. All terminal output and filesystem + interaction should be done by 'run()'. + + This method must be implemented by all command classes. + """ + raise RuntimeError( + "abstract method -- subclass %s must override" % self.__class__) + + # -- External interface -------------------------------------------- + # (called by outsiders) + + def get_source_files(self): + """Return the list of files that are used as inputs to this command, + i.e. the files used to generate the output files. The result is used + by the `sdist` command in determining the set of default files. + + Command classes should implement this method if they operate on files + from the source tree. + """ + return [] + + def get_outputs(self): + """Return the list of files that would be produced if this command + were actually run. Not affected by the "dry-run" flag or whether + any other commands have been run. + + Command classes should implement this method if they produce any + output files that get consumed by another command. e.g., `build_ext` + returns the list of built extension modules, but not any temporary + files used in the compilation process. + """ + return [] + + # -- Option validation methods ------------------------------------- + # (these are very handy in writing the 'finalize_options()' method) + # + # NB. the general philosophy here is to ensure that a particular option + # value meets certain type and value constraints. If not, we try to + # force it into conformance (eg. if we expect a list but have a string, + # split the string on comma and/or whitespace). If we can't force the + # option into conformance, raise PackagingOptionError. Thus, command + # classes need do nothing more than (eg.) + # self.ensure_string_list('foo') + # and they can be guaranteed that thereafter, self.foo will be + # a list of strings. + + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, str): + raise PackagingOptionError("'%s' must be a %s (got `%s`)" % + (option, what, val)) + return val + + def ensure_string(self, option, default=None): + """Ensure that 'option' is a string; if not defined, set it to + 'default'. + """ + self._ensure_stringlike(option, "string", default) + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, str): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + # checks if all elements are str + ok = True + for element in val: + if not isinstance(element, str): + ok = False + break + else: + ok = False + + if not ok: + raise PackagingOptionError( + "'%s' must be a list of strings (got %r)" % (option, val)) + + def _ensure_tested_string(self, option, tester, + what, error_fmt, default=None): + val = self._ensure_stringlike(option, what, default) + if val is not None and not tester(val): + raise PackagingOptionError( + ("error in '%s' option: " + error_fmt) % (option, val)) + + def ensure_filename(self, option): + """Ensure that 'option' is the name of an existing file.""" + self._ensure_tested_string(option, os.path.isfile, + "filename", + "'%s' does not exist or is not a file") + + def ensure_dirname(self, option): + self._ensure_tested_string(option, os.path.isdir, + "directory name", + "'%s' does not exist or is not a directory") + + # -- Convenience methods for commands ------------------------------ + + @classmethod + def get_command_name(cls): + if hasattr(cls, 'command_name'): + return cls.command_name + else: + return cls.__name__ + + def set_undefined_options(self, src_cmd, *options): + """Set values of undefined options from another command. + + Undefined options are options set to None, which is the convention + used to indicate that an option has not been changed between + 'initialize_options()' and 'finalize_options()'. This method is + usually called from 'finalize_options()' for options that depend on + some other command rather than another option of the same command, + typically subcommands. + + The 'src_cmd' argument is the other command from which option values + will be taken (a command object will be created for it if necessary); + the remaining positional arguments are strings that give the name of + the option to set. If the name is different on the source and target + command, you can pass a tuple with '(name_on_source, name_on_dest)' so + that 'self.name_on_dest' will be set from 'src_cmd.name_on_source'. + """ + src_cmd_obj = self.distribution.get_command_obj(src_cmd) + src_cmd_obj.ensure_finalized() + for obj in options: + if isinstance(obj, tuple): + src_option, dst_option = obj + else: + src_option, dst_option = obj, obj + if getattr(self, dst_option) is None: + setattr(self, dst_option, + getattr(src_cmd_obj, src_option)) + + def get_finalized_command(self, command, create=True): + """Wrapper around Distribution's 'get_command_obj()' method: find + (create if necessary and 'create' is true) the command object for + 'command', call its 'ensure_finalized()' method, and return the + finalized command object. + """ + cmd_obj = self.distribution.get_command_obj(command, create) + cmd_obj.ensure_finalized() + return cmd_obj + + def get_reinitialized_command(self, command, reinit_subcommands=False): + return self.distribution.get_reinitialized_command( + command, reinit_subcommands) + + def run_command(self, command): + """Run some other command: uses the 'run_command()' method of + Distribution, which creates and finalizes the command object if + necessary and then invokes its 'run()' method. + """ + self.distribution.run_command(command) + + def get_sub_commands(self): + """Determine the sub-commands that are relevant in the current + distribution (ie., that need to be run). This is based on the + 'sub_commands' class attribute: each tuple in that list may include + a method that we call to determine if the subcommand needs to be + run for the current distribution. Return a list of command names. + """ + commands = [] + for sub_command in self.sub_commands: + if len(sub_command) == 2: + cmd_name, method = sub_command + if method is None or method(self): + commands.append(cmd_name) + else: + commands.append(sub_command) + return commands + + # -- External world manipulation ----------------------------------- + + def execute(self, func, args, msg=None, level=1): + util.execute(func, args, msg, dry_run=self.dry_run) + + def mkpath(self, name, mode=0o777, dry_run=None, verbose=0): + if dry_run is None: + dry_run = self.dry_run + name = os.path.normpath(name) + if os.path.isdir(name) or name == '': + return + if dry_run: + head = '' + for part in name.split(os.sep): + logger.info("created directory %s%s", head, part) + head += part + os.sep + return + os.makedirs(name, mode) + + def copy_file(self, infile, outfile, + preserve_mode=True, preserve_times=True, link=None, level=1): + """Copy a file respecting verbose, dry-run and force flags. (The + former two default to whatever is in the Distribution object, and + the latter defaults to false for commands that don't define it.)""" + if self.dry_run: + # XXX add a comment + return + if os.path.isdir(outfile): + outfile = os.path.join(outfile, os.path.split(infile)[-1]) + copyfile(infile, outfile) + return outfile, None # XXX + + def copy_tree(self, infile, outfile, preserve_mode=True, + preserve_times=True, preserve_symlinks=False, level=1): + """Copy an entire directory tree respecting verbose, dry-run, + and force flags. + """ + if self.dry_run: + return # see if we want to display something + + + return util.copy_tree(infile, outfile, preserve_mode, preserve_times, + preserve_symlinks, not self.force, dry_run=self.dry_run) + + def move_file(self, src, dst, level=1): + """Move a file respecting the dry-run flag.""" + if self.dry_run: + return # XXX log ? + return move(src, dst) + + def spawn(self, cmd, search_path=True, level=1): + """Spawn an external command respecting dry-run flag.""" + from packaging.util import spawn + spawn(cmd, search_path, dry_run=self.dry_run) + + def make_archive(self, base_name, format, root_dir=None, base_dir=None, + owner=None, group=None): + return make_archive(base_name, format, root_dir, + base_dir, dry_run=self.dry_run, + owner=owner, group=group) + + def make_file(self, infiles, outfile, func, args, + exec_msg=None, skip_msg=None, level=1): + """Special case of 'execute()' for operations that process one or + more input files and generate one output file. Works just like + 'execute()', except the operation is skipped and a different + message printed if 'outfile' already exists and is newer than all + files listed in 'infiles'. If the command defined 'self.force', + and it is true, then the command is unconditionally run -- does no + timestamp checks. + """ + if skip_msg is None: + skip_msg = "skipping %s (inputs unchanged)" % outfile + + # Allow 'infiles' to be a single string + if isinstance(infiles, str): + infiles = (infiles,) + elif not isinstance(infiles, (list, tuple)): + raise TypeError( + "'infiles' must be a string, or a list or tuple of strings") + + if exec_msg is None: + exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles)) + + # If 'outfile' must be regenerated (either because it doesn't + # exist, is out-of-date, or the 'force' flag is true) then + # perform the action that presumably regenerates it + if self.force or util.newer_group(infiles, outfile): + self.execute(func, args, exec_msg, level) + + # Otherwise, print the "skip" message + else: + logger.debug(skip_msg) diff --git a/Lib/packaging/command/command_template b/Lib/packaging/command/command_template new file mode 100644 index 0000000000..a12d32bfb3 --- /dev/null +++ b/Lib/packaging/command/command_template @@ -0,0 +1,35 @@ +"""Do X and Y.""" + +from packaging import logger +from packaging.command.cmd import Command + + +class x(Command): + + # Brief (40-50 characters) description of the command + description = "" + + # List of option tuples: long name, short name (None if no short + # name), and help string. + user_options = [ + ('', '', # long option, short option (one letter) or None + ""), # help text + ] + + def initialize_options(self): + self. = None + self. = None + self. = None + + def finalize_options(self): + if self.x is None: + self.x = ... + + def run(self): + ... + logger.info(...) + + if not self.dry_run: + ... + + self.execute(..., dry_run=self.dry_run) diff --git a/Lib/packaging/command/config.py b/Lib/packaging/command/config.py new file mode 100644 index 0000000000..a5feb9139a --- /dev/null +++ b/Lib/packaging/command/config.py @@ -0,0 +1,351 @@ +"""Prepare the build. + +This module provides config, a (mostly) empty command class +that exists mainly to be sub-classed by specific module distributions and +applications. The idea is that while every "config" command is different, +at least they're all named the same, and users always see "config" in the +list of standard commands. Also, this is a good place to put common +configure-like tasks: "try to compile this C code", or "figure out where +this header file lives". +""" + +import os +import re + +from packaging.command.cmd import Command +from packaging.errors import PackagingExecError +from packaging.compiler import customize_compiler +from packaging import logger + +LANG_EXT = {'c': '.c', 'c++': '.cxx'} + +class config(Command): + + description = "prepare the build" + + user_options = [ + ('compiler=', None, + "specify the compiler type"), + ('cc=', None, + "specify the compiler executable"), + ('include-dirs=', 'I', + "list of directories to search for header files"), + ('define=', 'D', + "C preprocessor macros to define"), + ('undef=', 'U', + "C preprocessor macros to undefine"), + ('libraries=', 'l', + "external C libraries to link with"), + ('library-dirs=', 'L', + "directories to search for external C libraries"), + + ('noisy', None, + "show every action (compile, link, run, ...) taken"), + ('dump-source', None, + "dump generated source files before attempting to compile them"), + ] + + + # The three standard command methods: since the "config" command + # does nothing by default, these are empty. + + def initialize_options(self): + self.compiler = None + self.cc = None + self.include_dirs = None + self.libraries = None + self.library_dirs = None + + # maximal output for now + self.noisy = True + self.dump_source = True + + # list of temporary files generated along-the-way that we have + # to clean at some point + self.temp_files = [] + + def finalize_options(self): + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + elif isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + if self.libraries is None: + self.libraries = [] + elif isinstance(self.libraries, str): + self.libraries = [self.libraries] + + if self.library_dirs is None: + self.library_dirs = [] + elif isinstance(self.library_dirs, str): + self.library_dirs = self.library_dirs.split(os.pathsep) + + def run(self): + pass + + + # Utility methods for actual "config" commands. The interfaces are + # loosely based on Autoconf macros of similar names. Sub-classes + # may use these freely. + + def _check_compiler(self): + """Check that 'self.compiler' really is a CCompiler object; + if not, make it one. + """ + # We do this late, and only on-demand, because this is an expensive + # import. + from packaging.compiler.ccompiler import CCompiler + from packaging.compiler import new_compiler + if not isinstance(self.compiler, CCompiler): + self.compiler = new_compiler(compiler=self.compiler, + dry_run=self.dry_run, force=True) + customize_compiler(self.compiler) + if self.include_dirs: + self.compiler.set_include_dirs(self.include_dirs) + if self.libraries: + self.compiler.set_libraries(self.libraries) + if self.library_dirs: + self.compiler.set_library_dirs(self.library_dirs) + + + def _gen_temp_sourcefile(self, body, headers, lang): + filename = "_configtest" + LANG_EXT[lang] + file = open(filename, "w") + if headers: + for header in headers: + file.write("#include <%s>\n" % header) + file.write("\n") + file.write(body) + if body[-1] != "\n": + file.write("\n") + file.close() + return filename + + def _preprocess(self, body, headers, include_dirs, lang): + src = self._gen_temp_sourcefile(body, headers, lang) + out = "_configtest.i" + self.temp_files.extend((src, out)) + self.compiler.preprocess(src, out, include_dirs=include_dirs) + return src, out + + def _compile(self, body, headers, include_dirs, lang): + src = self._gen_temp_sourcefile(body, headers, lang) + if self.dump_source: + dump_file(src, "compiling '%s':" % src) + obj = self.compiler.object_filenames([src])[0] + self.temp_files.extend((src, obj)) + self.compiler.compile([src], include_dirs=include_dirs) + return src, obj + + def _link(self, body, headers, include_dirs, libraries, library_dirs, + lang): + src, obj = self._compile(body, headers, include_dirs, lang) + prog = os.path.splitext(os.path.basename(src))[0] + self.compiler.link_executable([obj], prog, + libraries=libraries, + library_dirs=library_dirs, + target_lang=lang) + + if self.compiler.exe_extension is not None: + prog = prog + self.compiler.exe_extension + self.temp_files.append(prog) + + return src, obj, prog + + def _clean(self, *filenames): + if not filenames: + filenames = self.temp_files + self.temp_files = [] + logger.info("removing: %s", ' '.join(filenames)) + for filename in filenames: + try: + os.remove(filename) + except OSError: + pass + + + # XXX these ignore the dry-run flag: what to do, what to do? even if + # you want a dry-run build, you still need some sort of configuration + # info. My inclination is to make it up to the real config command to + # consult 'dry_run', and assume a default (minimal) configuration if + # true. The problem with trying to do it here is that you'd have to + # return either true or false from all the 'try' methods, neither of + # which is correct. + + # XXX need access to the header search path and maybe default macros. + + def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): + """Construct a source file from 'body' (a string containing lines + of C/C++ code) and 'headers' (a list of header files to include) + and run it through the preprocessor. Return true if the + preprocessor succeeded, false if there were any errors. + ('body' probably isn't of much use, but what the heck.) + """ + from packaging.compiler.ccompiler import CompileError + self._check_compiler() + ok = True + try: + self._preprocess(body, headers, include_dirs, lang) + except CompileError: + ok = False + + self._clean() + return ok + + def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, + lang="c"): + """Construct a source file (just like 'try_cpp()'), run it through + the preprocessor, and return true if any line of the output matches + 'pattern'. 'pattern' should either be a compiled regex object or a + string containing a regex. If both 'body' and 'headers' are None, + preprocesses an empty file -- which can be useful to determine the + symbols the preprocessor and compiler set by default. + """ + self._check_compiler() + src, out = self._preprocess(body, headers, include_dirs, lang) + + if isinstance(pattern, str): + pattern = re.compile(pattern) + + file = open(out) + match = False + while True: + line = file.readline() + if line == '': + break + if pattern.search(line): + match = True + break + + file.close() + self._clean() + return match + + def try_compile(self, body, headers=None, include_dirs=None, lang="c"): + """Try to compile a source file built from 'body' and 'headers'. + Return true on success, false otherwise. + """ + from packaging.compiler.ccompiler import CompileError + self._check_compiler() + try: + self._compile(body, headers, include_dirs, lang) + ok = True + except CompileError: + ok = False + + logger.info(ok and "success!" or "failure.") + self._clean() + return ok + + def try_link(self, body, headers=None, include_dirs=None, libraries=None, + library_dirs=None, lang="c"): + """Try to compile and link a source file, built from 'body' and + 'headers', to executable form. Return true on success, false + otherwise. + """ + from packaging.compiler.ccompiler import CompileError, LinkError + self._check_compiler() + try: + self._link(body, headers, include_dirs, + libraries, library_dirs, lang) + ok = True + except (CompileError, LinkError): + ok = False + + logger.info(ok and "success!" or "failure.") + self._clean() + return ok + + def try_run(self, body, headers=None, include_dirs=None, libraries=None, + library_dirs=None, lang="c"): + """Try to compile, link to an executable, and run a program + built from 'body' and 'headers'. Return true on success, false + otherwise. + """ + from packaging.compiler.ccompiler import CompileError, LinkError + self._check_compiler() + try: + src, obj, exe = self._link(body, headers, include_dirs, + libraries, library_dirs, lang) + self.spawn([exe]) + ok = True + except (CompileError, LinkError, PackagingExecError): + ok = False + + logger.info(ok and "success!" or "failure.") + self._clean() + return ok + + + # -- High-level methods -------------------------------------------- + # (these are the ones that are actually likely to be useful + # when implementing a real-world config command!) + + def check_func(self, func, headers=None, include_dirs=None, + libraries=None, library_dirs=None, decl=False, call=False): + + """Determine if function 'func' is available by constructing a + source file that refers to 'func', and compiles and links it. + If everything succeeds, returns true; otherwise returns false. + + The constructed source file starts out by including the header + files listed in 'headers'. If 'decl' is true, it then declares + 'func' (as "int func()"); you probably shouldn't supply 'headers' + and set 'decl' true in the same call, or you might get errors about + a conflicting declarations for 'func'. Finally, the constructed + 'main()' function either references 'func' or (if 'call' is true) + calls it. 'libraries' and 'library_dirs' are used when + linking. + """ + + self._check_compiler() + body = [] + if decl: + body.append("int %s ();" % func) + body.append("int main () {") + if call: + body.append(" %s();" % func) + else: + body.append(" %s;" % func) + body.append("}") + body = "\n".join(body) + "\n" + + return self.try_link(body, headers, include_dirs, + libraries, library_dirs) + + def check_lib(self, library, library_dirs=None, headers=None, + include_dirs=None, other_libraries=[]): + """Determine if 'library' is available to be linked against, + without actually checking that any particular symbols are provided + by it. 'headers' will be used in constructing the source file to + be compiled, but the only effect of this is to check if all the + header files listed are available. Any libraries listed in + 'other_libraries' will be included in the link, in case 'library' + has symbols that depend on other libraries. + """ + self._check_compiler() + return self.try_link("int main (void) { }", + headers, include_dirs, + [library]+other_libraries, library_dirs) + + def check_header(self, header, include_dirs=None, library_dirs=None, + lang="c"): + """Determine if the system header file named by 'header_file' + exists and can be found by the preprocessor; return true if so, + false otherwise. + """ + return self.try_cpp(body="/* No body */", headers=[header], + include_dirs=include_dirs) + + +def dump_file(filename, head=None): + """Dumps a file content into log.info. + + If head is not None, will be dumped before the file content. + """ + if head is None: + logger.info(filename) + else: + logger.info(head) + with open(filename) as file: + logger.info(file.read()) diff --git a/Lib/packaging/command/install_data.py b/Lib/packaging/command/install_data.py new file mode 100644 index 0000000000..9ca6279533 --- /dev/null +++ b/Lib/packaging/command/install_data.py @@ -0,0 +1,79 @@ +"""Install platform-independent data files.""" + +# Contributed by Bastian Kleineidam + +import os +from shutil import Error +from sysconfig import get_paths, format_value +from packaging import logger +from packaging.util import convert_path +from packaging.command.cmd import Command + + +class install_data(Command): + + description = "install platform-independent data files" + + user_options = [ + ('install-dir=', 'd', + "base directory for installing data files " + "(default: installation base dir)"), + ('root=', None, + "install everything relative to this alternate root directory"), + ('force', 'f', "force installation (overwrite existing files)"), + ] + + boolean_options = ['force'] + + def initialize_options(self): + self.install_dir = None + self.outfiles = [] + self.data_files_out = [] + self.root = None + self.force = False + self.data_files = self.distribution.data_files + self.warn_dir = True + + def finalize_options(self): + self.set_undefined_options('install_dist', + ('install_data', 'install_dir'), + 'root', 'force') + + def run(self): + self.mkpath(self.install_dir) + for _file in self.data_files.items(): + destination = convert_path(self.expand_categories(_file[1])) + dir_dest = os.path.abspath(os.path.dirname(destination)) + + self.mkpath(dir_dest) + try: + out = self.copy_file(_file[0], dir_dest)[0] + except Error as e: + logger.warning('%s: %s', self.get_command_name(), e) + out = destination + + self.outfiles.append(out) + self.data_files_out.append((_file[0], destination)) + + def expand_categories(self, path_with_categories): + local_vars = get_paths() + local_vars['distribution.name'] = self.distribution.metadata['Name'] + expanded_path = format_value(path_with_categories, local_vars) + expanded_path = format_value(expanded_path, local_vars) + if '{' in expanded_path and '}' in expanded_path: + logger.warning( + '%s: unable to expand %s, some categories may be missing', + self.get_command_name(), path_with_categories) + return expanded_path + + def get_source_files(self): + return list(self.data_files) + + def get_inputs(self): + return list(self.data_files) + + def get_outputs(self): + return self.outfiles + + def get_resources_out(self): + return self.data_files_out diff --git a/Lib/packaging/command/install_dist.py b/Lib/packaging/command/install_dist.py new file mode 100644 index 0000000000..dfe6df2d5a --- /dev/null +++ b/Lib/packaging/command/install_dist.py @@ -0,0 +1,625 @@ +"""Main install command, which calls the other install_* commands.""" + +import sys +import os + +import sysconfig +from sysconfig import get_config_vars, get_paths, get_path, get_config_var + +from packaging import logger +from packaging.command.cmd import Command +from packaging.errors import PackagingPlatformError +from packaging.util import write_file +from packaging.util import convert_path, change_root, get_platform +from packaging.errors import PackagingOptionError + + +HAS_USER_SITE = True + + +class install_dist(Command): + + description = "install everything from build directory" + + user_options = [ + # Select installation scheme and set base director(y|ies) + ('prefix=', None, + "installation prefix"), + ('exec-prefix=', None, + "(Unix only) prefix for platform-specific files"), + ('home=', None, + "(Unix only) home directory to install under"), + + # Or just set the base director(y|ies) + ('install-base=', None, + "base installation directory (instead of --prefix or --home)"), + ('install-platbase=', None, + "base installation directory for platform-specific files " + + "(instead of --exec-prefix or --home)"), + ('root=', None, + "install everything relative to this alternate root directory"), + + # Or explicitly set the installation scheme + ('install-purelib=', None, + "installation directory for pure Python module distributions"), + ('install-platlib=', None, + "installation directory for non-pure module distributions"), + ('install-lib=', None, + "installation directory for all module distributions " + + "(overrides --install-purelib and --install-platlib)"), + + ('install-headers=', None, + "installation directory for C/C++ headers"), + ('install-scripts=', None, + "installation directory for Python scripts"), + ('install-data=', None, + "installation directory for data files"), + + # Byte-compilation options -- see install_lib.py for details, as + # these are duplicated from there (but only install_lib does + # anything with them). + ('compile', 'c', "compile .py to .pyc [default]"), + ('no-compile', None, "don't compile .py files"), + ('optimize=', 'O', + 'also compile with optimization: -O1 for "python -O", ' + '-O2 for "python -OO", and -O0 to disable [default: -O0]'), + + # Miscellaneous control options + ('force', 'f', + "force installation (overwrite any existing files)"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + + # Where to install documentation (eventually!) + #('doc-format=', None, "format of documentation to generate"), + #('install-man=', None, "directory for Unix man pages"), + #('install-html=', None, "directory for HTML documentation"), + #('install-info=', None, "directory for GNU info files"), + + # XXX use a name that makes clear this is the old format + ('record=', None, + "filename in which to record a list of installed files " + "(not PEP 376-compliant)"), + ('resources=', None, + "data files mapping"), + + # .dist-info related arguments, read by install_dist_info + ('no-distinfo', None, + "do not create a .dist-info directory"), + ('installer=', None, + "the name of the installer"), + ('requested', None, + "generate a REQUESTED file (i.e."), + ('no-requested', None, + "do not generate a REQUESTED file"), + ('no-record', None, + "do not generate a RECORD file"), + ] + + boolean_options = ['compile', 'force', 'skip-build', 'no-distinfo', + 'requested', 'no-record'] + + if HAS_USER_SITE: + user_options.append( + ('user', None, + "install in user site-packages directory [%s]" % + get_path('purelib', '%s_user' % os.name))) + + boolean_options.append('user') + + negative_opt = {'no-compile': 'compile', 'no-requested': 'requested'} + + def initialize_options(self): + # High-level options: these select both an installation base + # and scheme. + self.prefix = None + self.exec_prefix = None + self.home = None + if HAS_USER_SITE: + self.user = False + + # These select only the installation base; it's up to the user to + # specify the installation scheme (currently, that means supplying + # the --install-{platlib,purelib,scripts,data} options). + self.install_base = None + self.install_platbase = None + self.root = None + + # These options are the actual installation directories; if not + # supplied by the user, they are filled in using the installation + # scheme implied by prefix/exec-prefix/home and the contents of + # that installation scheme. + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + if HAS_USER_SITE: + self.install_userbase = get_config_var('userbase') + self.install_usersite = get_path('purelib', '%s_user' % os.name) + + self.compile = None + self.optimize = None + + # These two are for putting non-packagized distributions into their + # own directory and creating a .pth file if it makes sense. + # 'extra_path' comes from the setup file; 'install_path_file' can + # be turned off if it makes no sense to install a .pth file. (But + # better to install it uselessly than to guess wrong and not + # install it when it's necessary and would be used!) Currently, + # 'install_path_file' is always true unless some outsider meddles + # with it. + self.extra_path = None + self.install_path_file = True + + # 'force' forces installation, even if target files are not + # out-of-date. 'skip_build' skips running the "build" command, + # handy if you know it's not necessary. 'warn_dir' (which is *not* + # a user option, it's just there so the bdist_* commands can turn + # it off) determines whether we warn about installing to a + # directory not in sys.path. + self.force = False + self.skip_build = False + self.warn_dir = True + + # These are only here as a conduit from the 'build' command to the + # 'install_*' commands that do the real work. ('build_base' isn't + # actually used anywhere, but it might be useful in future.) They + # are not user options, because if the user told the install + # command where the build directory is, that wouldn't affect the + # build command. + self.build_base = None + self.build_lib = None + + # Not defined yet because we don't know anything about + # documentation yet. + #self.install_man = None + #self.install_html = None + #self.install_info = None + + self.record = None + self.resources = None + + # .dist-info related options + self.no_distinfo = None + self.installer = None + self.requested = None + self.no_record = None + self.no_resources = None + + # -- Option finalizing methods ------------------------------------- + # (This is rather more involved than for most commands, + # because this is where the policy for installing third- + # party Python modules on various platforms given a wide + # array of user input is decided. Yes, it's quite complex!) + + def finalize_options(self): + # This method (and its pliant slaves, like 'finalize_unix()', + # 'finalize_other()', and 'select_scheme()') is where the default + # installation directories for modules, extension modules, and + # anything else we care to install from a Python module + # distribution. Thus, this code makes a pretty important policy + # statement about how third-party stuff is added to a Python + # installation! Note that the actual work of installation is done + # by the relatively simple 'install_*' commands; they just take + # their orders from the installation directory options determined + # here. + + # Check for errors/inconsistencies in the options; first, stuff + # that's wrong on any platform. + + if ((self.prefix or self.exec_prefix or self.home) and + (self.install_base or self.install_platbase)): + raise PackagingOptionError( + "must supply either prefix/exec-prefix/home or " + "install-base/install-platbase -- not both") + + if self.home and (self.prefix or self.exec_prefix): + raise PackagingOptionError( + "must supply either home or prefix/exec-prefix -- not both") + + if HAS_USER_SITE and self.user and ( + self.prefix or self.exec_prefix or self.home or + self.install_base or self.install_platbase): + raise PackagingOptionError( + "can't combine user with prefix/exec_prefix/home or " + "install_base/install_platbase") + + # Next, stuff that's wrong (or dubious) only on certain platforms. + if os.name != "posix": + if self.exec_prefix: + logger.warning( + '%s: exec-prefix option ignored on this platform', + self.get_command_name()) + self.exec_prefix = None + + # Now the interesting logic -- so interesting that we farm it out + # to other methods. The goal of these methods is to set the final + # values for the install_{lib,scripts,data,...} options, using as + # input a heady brew of prefix, exec_prefix, home, install_base, + # install_platbase, user-supplied versions of + # install_{purelib,platlib,lib,scripts,data,...}, and the + # INSTALL_SCHEME dictionary above. Phew! + + self.dump_dirs("pre-finalize_{unix,other}") + + if os.name == 'posix': + self.finalize_unix() + else: + self.finalize_other() + + self.dump_dirs("post-finalize_{unix,other}()") + + # Expand configuration variables, tilde, etc. in self.install_base + # and self.install_platbase -- that way, we can use $base or + # $platbase in the other installation directories and not worry + # about needing recursive variable expansion (shudder). + + py_version = sys.version.split()[0] + prefix, exec_prefix, srcdir, projectbase = get_config_vars( + 'prefix', 'exec_prefix', 'srcdir', 'projectbase') + + metadata = self.distribution.metadata + self.config_vars = { + 'dist_name': metadata['Name'], + 'dist_version': metadata['Version'], + 'dist_fullname': metadata.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[:3], + 'py_version_nodot': py_version[:3:2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + 'srcdir': srcdir, + 'projectbase': projectbase, + } + + if HAS_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + self.expand_basedirs() + + self.dump_dirs("post-expand_basedirs()") + + # Now define config vars for the base directories so we can expand + # everything else. + self.config_vars['base'] = self.install_base + self.config_vars['platbase'] = self.install_platbase + + # Expand "~" and configuration variables in the installation + # directories. + self.expand_dirs() + + self.dump_dirs("post-expand_dirs()") + + # Create directories in the home dir: + if HAS_USER_SITE and self.user: + self.create_home_path() + + # Pick the actual directory to install all modules to: either + # install_purelib or install_platlib, depending on whether this + # module distribution is pure or not. Of course, if the user + # already specified install_lib, use their selection. + if self.install_lib is None: + if self.distribution.ext_modules: # has extensions: non-pure + self.install_lib = self.install_platlib + else: + self.install_lib = self.install_purelib + + # Convert directories from Unix /-separated syntax to the local + # convention. + self.convert_paths('lib', 'purelib', 'platlib', + 'scripts', 'data', 'headers') + if HAS_USER_SITE: + self.convert_paths('userbase', 'usersite') + + # Well, we're not actually fully completely finalized yet: we still + # have to deal with 'extra_path', which is the hack for allowing + # non-packagized module distributions (hello, Numerical Python!) to + # get their own directories. + self.handle_extra_path() + self.install_libbase = self.install_lib # needed for .pth file + self.install_lib = os.path.join(self.install_lib, self.extra_dirs) + + # If a new root directory was supplied, make all the installation + # dirs relative to it. + if self.root is not None: + self.change_roots('libbase', 'lib', 'purelib', 'platlib', + 'scripts', 'data', 'headers') + + self.dump_dirs("after prepending root") + + # Find out the build directories, ie. where to install from. + self.set_undefined_options('build', 'build_base', 'build_lib') + + # Punt on doc directories for now -- after all, we're punting on + # documentation completely! + + if self.no_distinfo is None: + self.no_distinfo = False + + def finalize_unix(self): + """Finalize options for posix platforms.""" + if self.install_base is not None or self.install_platbase is not None: + if ((self.install_lib is None and + self.install_purelib is None and + self.install_platlib is None) or + self.install_headers is None or + self.install_scripts is None or + self.install_data is None): + raise PackagingOptionError( + "install-base or install-platbase supplied, but " + "installation scheme is incomplete") + return + + if HAS_USER_SITE and self.user: + if self.install_userbase is None: + raise PackagingPlatformError( + "user base directory is not specified") + self.install_base = self.install_platbase = self.install_userbase + self.select_scheme("posix_user") + elif self.home is not None: + self.install_base = self.install_platbase = self.home + self.select_scheme("posix_home") + else: + if self.prefix is None: + if self.exec_prefix is not None: + raise PackagingOptionError( + "must not supply exec-prefix without prefix") + + self.prefix = os.path.normpath(sys.prefix) + self.exec_prefix = os.path.normpath(sys.exec_prefix) + + else: + if self.exec_prefix is None: + self.exec_prefix = self.prefix + + self.install_base = self.prefix + self.install_platbase = self.exec_prefix + self.select_scheme("posix_prefix") + + def finalize_other(self): + """Finalize options for non-posix platforms""" + if HAS_USER_SITE and self.user: + if self.install_userbase is None: + raise PackagingPlatformError( + "user base directory is not specified") + self.install_base = self.install_platbase = self.install_userbase + self.select_scheme(os.name + "_user") + elif self.home is not None: + self.install_base = self.install_platbase = self.home + self.select_scheme("posix_home") + else: + if self.prefix is None: + self.prefix = os.path.normpath(sys.prefix) + + self.install_base = self.install_platbase = self.prefix + try: + self.select_scheme(os.name) + except KeyError: + raise PackagingPlatformError( + "no support for installation on '%s'" % os.name) + + def dump_dirs(self, msg): + """Dump the list of user options.""" + logger.debug(msg + ":") + for opt in self.user_options: + opt_name = opt[0] + if opt_name[-1] == "=": + opt_name = opt_name[0:-1] + if opt_name in self.negative_opt: + opt_name = self.negative_opt[opt_name] + opt_name = opt_name.replace('-', '_') + val = not getattr(self, opt_name) + else: + opt_name = opt_name.replace('-', '_') + val = getattr(self, opt_name) + logger.debug(" %s: %s", opt_name, val) + + def select_scheme(self, name): + """Set the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + scheme = get_paths(name, expand=False) + for key, value in scheme.items(): + if key == 'platinclude': + key = 'headers' + value = os.path.join(value, self.distribution.metadata['Name']) + attrname = 'install_' + key + if hasattr(self, attrname): + if getattr(self, attrname) is None: + setattr(self, attrname, value) + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + # see if we want to push this work in sysconfig XXX + val = sysconfig._subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Call `os.path.expanduser` on install_{base,platbase} and root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Call `os.path.expanduser` on install dirs.""" + self._expand_attrs(['install_purelib', 'install_platlib', + 'install_lib', 'install_headers', + 'install_scripts', 'install_data']) + + def convert_paths(self, *names): + """Call `convert_path` over `names`.""" + for name in names: + attr = "install_" + name + setattr(self, attr, convert_path(getattr(self, attr))) + + def handle_extra_path(self): + """Set `path_file` and `extra_dirs` using `extra_path`.""" + if self.extra_path is None: + self.extra_path = self.distribution.extra_path + + if self.extra_path is not None: + if isinstance(self.extra_path, str): + self.extra_path = self.extra_path.split(',') + + if len(self.extra_path) == 1: + path_file = extra_dirs = self.extra_path[0] + elif len(self.extra_path) == 2: + path_file, extra_dirs = self.extra_path + else: + raise PackagingOptionError( + "'extra_path' option must be a list, tuple, or " + "comma-separated string with 1 or 2 elements") + + # convert to local form in case Unix notation used (as it + # should be in setup scripts) + extra_dirs = convert_path(extra_dirs) + else: + path_file = None + extra_dirs = '' + + # XXX should we warn if path_file and not extra_dirs? (in which + # case the path file would be harmless but pointless) + self.path_file = path_file + self.extra_dirs = extra_dirs + + def change_roots(self, *names): + """Change the install direcories pointed by name using root.""" + for name in names: + attr = "install_" + name + setattr(self, attr, change_root(self.root, getattr(self, attr))) + + def create_home_path(self): + """Create directories under ~.""" + if HAS_USER_SITE and not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in self.config_vars.items(): + if path.startswith(home) and not os.path.isdir(path): + os.makedirs(path, 0o700) + + # -- Command execution methods ------------------------------------- + + def run(self): + """Runs the command.""" + # Obviously have to build before we can install + if not self.skip_build: + self.run_command('build') + # If we built for any other platform, we can't install. + build_plat = self.distribution.get_command_obj('build').plat_name + # check warn_dir - it is a clue that the 'install_dist' is happening + # internally, and not to sys.path, so we don't check the platform + # matches what we are running. + if self.warn_dir and build_plat != get_platform(): + raise PackagingPlatformError("Can't install when " + "cross-compiling") + + # Run all sub-commands (at least those that need to be run) + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + if self.path_file: + self.create_path_file() + + # write list of installed files, if requested. + if self.record: + outputs = self.get_outputs() + if self.root: # strip any package prefix + root_len = len(self.root) + for counter in range(len(outputs)): + outputs[counter] = outputs[counter][root_len:] + self.execute(write_file, + (self.record, outputs), + "writing list of installed files to '%s'" % + self.record) + + normpath, normcase = os.path.normpath, os.path.normcase + sys_path = [normcase(normpath(p)) for p in sys.path] + install_lib = normcase(normpath(self.install_lib)) + if (self.warn_dir and + not (self.path_file and self.install_path_file) and + install_lib not in sys_path): + logger.debug(("modules installed to '%s', which is not in " + "Python's module search path (sys.path) -- " + "you'll have to change the search path yourself"), + self.install_lib) + + def create_path_file(self): + """Creates the .pth file""" + filename = os.path.join(self.install_libbase, + self.path_file + ".pth") + if self.install_path_file: + self.execute(write_file, + (filename, [self.extra_dirs]), + "creating %s" % filename) + else: + logger.warning('%s: path file %r not created', + self.get_command_name(), filename) + + # -- Reporting methods --------------------------------------------- + + def get_outputs(self): + """Assembles the outputs of all the sub-commands.""" + outputs = [] + for cmd_name in self.get_sub_commands(): + cmd = self.get_finalized_command(cmd_name) + # Add the contents of cmd.get_outputs(), ensuring + # that outputs doesn't contain duplicate entries + for filename in cmd.get_outputs(): + if filename not in outputs: + outputs.append(filename) + + if self.path_file and self.install_path_file: + outputs.append(os.path.join(self.install_libbase, + self.path_file + ".pth")) + + return outputs + + def get_inputs(self): + """Returns the inputs of all the sub-commands""" + # XXX gee, this looks familiar ;-( + inputs = [] + for cmd_name in self.get_sub_commands(): + cmd = self.get_finalized_command(cmd_name) + inputs.extend(cmd.get_inputs()) + + return inputs + + # -- Predicates for sub-command list ------------------------------- + + def has_lib(self): + """Returns true if the current distribution has any Python + modules to install.""" + return (self.distribution.has_pure_modules() or + self.distribution.has_ext_modules()) + + def has_headers(self): + """Returns true if the current distribution has any headers to + install.""" + return self.distribution.has_headers() + + def has_scripts(self): + """Returns true if the current distribution has any scripts to. + install.""" + return self.distribution.has_scripts() + + def has_data(self): + """Returns true if the current distribution has any data to. + install.""" + return self.distribution.has_data_files() + + # 'sub_commands': a list of commands this command might have to run to + # get its work done. See cmd.py for more info. + sub_commands = [('install_lib', has_lib), + ('install_headers', has_headers), + ('install_scripts', has_scripts), + ('install_data', has_data), + # keep install_distinfo last, as it needs the record + # with files to be completely generated + ('install_distinfo', lambda self: not self.no_distinfo), + ] diff --git a/Lib/packaging/command/install_distinfo.py b/Lib/packaging/command/install_distinfo.py new file mode 100644 index 0000000000..41fe73459f --- /dev/null +++ b/Lib/packaging/command/install_distinfo.py @@ -0,0 +1,175 @@ +"""Create the PEP 376-compliant .dist-info directory.""" + +# Forked from the former install_egg_info command by Josip Djolonga + +import csv +import os +import re +import hashlib + +from packaging.command.cmd import Command +from packaging import logger +from shutil import rmtree + + +class install_distinfo(Command): + + description = 'create a .dist-info directory for the distribution' + + user_options = [ + ('distinfo-dir=', None, + "directory where the the .dist-info directory will be installed"), + ('installer=', None, + "the name of the installer"), + ('requested', None, + "generate a REQUESTED file"), + ('no-requested', None, + "do not generate a REQUESTED file"), + ('no-record', None, + "do not generate a RECORD file"), + ('no-resources', None, + "do not generate a RESSOURCES list installed file") + ] + + boolean_options = ['requested', 'no-record', 'no-resources'] + + negative_opt = {'no-requested': 'requested'} + + def initialize_options(self): + self.distinfo_dir = None + self.installer = None + self.requested = None + self.no_record = None + self.no_resources = None + + def finalize_options(self): + self.set_undefined_options('install_dist', + 'installer', 'requested', 'no_record') + + self.set_undefined_options('install_lib', + ('install_dir', 'distinfo_dir')) + + if self.installer is None: + # FIXME distutils or packaging? + # + document default in the option help text above and in install + self.installer = 'distutils' + if self.requested is None: + self.requested = True + if self.no_record is None: + self.no_record = False + if self.no_resources is None: + self.no_resources = False + + metadata = self.distribution.metadata + + basename = "%s-%s.dist-info" % ( + to_filename(safe_name(metadata['Name'])), + to_filename(safe_version(metadata['Version']))) + + self.distinfo_dir = os.path.join(self.distinfo_dir, basename) + self.outputs = [] + + def run(self): + # FIXME dry-run should be used at a finer level, so that people get + # useful logging output and can have an idea of what the command would + # have done + if not self.dry_run: + target = self.distinfo_dir + + if os.path.isdir(target) and not os.path.islink(target): + rmtree(target) + elif os.path.exists(target): + self.execute(os.unlink, (self.distinfo_dir,), + "removing " + target) + + self.execute(os.makedirs, (target,), "creating " + target) + + metadata_path = os.path.join(self.distinfo_dir, 'METADATA') + logger.info('creating %s', metadata_path) + self.distribution.metadata.write(metadata_path) + self.outputs.append(metadata_path) + + installer_path = os.path.join(self.distinfo_dir, 'INSTALLER') + logger.info('creating %s', installer_path) + with open(installer_path, 'w') as f: + f.write(self.installer) + self.outputs.append(installer_path) + + if self.requested: + requested_path = os.path.join(self.distinfo_dir, 'REQUESTED') + logger.info('creating %s', requested_path) + open(requested_path, 'w').close() + self.outputs.append(requested_path) + + + if not self.no_resources: + install_data = self.get_finalized_command('install_data') + if install_data.get_resources_out() != []: + resources_path = os.path.join(self.distinfo_dir, + 'RESOURCES') + logger.info('creating %s', resources_path) + with open(resources_path, 'wb') as f: + writer = csv.writer(f, delimiter=',', + lineterminator=os.linesep, + quotechar='"') + for tuple in install_data.get_resources_out(): + writer.writerow(tuple) + + self.outputs.append(resources_path) + + if not self.no_record: + record_path = os.path.join(self.distinfo_dir, 'RECORD') + logger.info('creating %s', record_path) + with open(record_path, 'w', encoding='utf-8') as f: + writer = csv.writer(f, delimiter=',', + lineterminator=os.linesep, + quotechar='"') + + install = self.get_finalized_command('install_dist') + + for fpath in install.get_outputs(): + if fpath.endswith('.pyc') or fpath.endswith('.pyo'): + # do not put size and md5 hash, as in PEP-376 + writer.writerow((fpath, '', '')) + else: + size = os.path.getsize(fpath) + with open(fpath, 'r') as fp: + hash = hashlib.md5() + hash.update(fp.read().encode()) + md5sum = hash.hexdigest() + writer.writerow((fpath, md5sum, size)) + + # add the RECORD file itself + writer.writerow((record_path, '', '')) + self.outputs.append(record_path) + + def get_outputs(self): + return self.outputs + + +# The following functions are taken from setuptools' pkg_resources module. + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """Convert an arbitrary string to a standard version string + + Spaces become dots, and all other non-alphanumeric characters become + dashes, with runs of multiple dashes condensed to a single dash. + """ + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-', '_') diff --git a/Lib/packaging/command/install_headers.py b/Lib/packaging/command/install_headers.py new file mode 100644 index 0000000000..e043d6b8ed --- /dev/null +++ b/Lib/packaging/command/install_headers.py @@ -0,0 +1,43 @@ +"""Install C/C++ header files to the Python include directory.""" + +from packaging.command.cmd import Command + + +# XXX force is never used +class install_headers(Command): + + description = "install C/C++ header files" + + user_options = [('install-dir=', 'd', + "directory to install header files to"), + ('force', 'f', + "force installation (overwrite existing files)"), + ] + + boolean_options = ['force'] + + def initialize_options(self): + self.install_dir = None + self.force = False + self.outfiles = [] + + def finalize_options(self): + self.set_undefined_options('install_dist', + ('install_headers', 'install_dir'), + 'force') + + def run(self): + headers = self.distribution.headers + if not headers: + return + + self.mkpath(self.install_dir) + for header in headers: + out = self.copy_file(header, self.install_dir)[0] + self.outfiles.append(out) + + def get_inputs(self): + return self.distribution.headers or [] + + def get_outputs(self): + return self.outfiles diff --git a/Lib/packaging/command/install_lib.py b/Lib/packaging/command/install_lib.py new file mode 100644 index 0000000000..5ff9cee03c --- /dev/null +++ b/Lib/packaging/command/install_lib.py @@ -0,0 +1,222 @@ +"""Install all modules (extensions and pure Python).""" + +import os +import sys +import logging + +from packaging import logger +from packaging.command.cmd import Command +from packaging.errors import PackagingOptionError + + +# Extension for Python source files. +if hasattr(os, 'extsep'): + PYTHON_SOURCE_EXTENSION = os.extsep + "py" +else: + PYTHON_SOURCE_EXTENSION = ".py" + +class install_lib(Command): + + description = "install all modules (extensions and pure Python)" + + # The byte-compilation options are a tad confusing. Here are the + # possible scenarios: + # 1) no compilation at all (--no-compile --no-optimize) + # 2) compile .pyc only (--compile --no-optimize; default) + # 3) compile .pyc and "level 1" .pyo (--compile --optimize) + # 4) compile "level 1" .pyo only (--no-compile --optimize) + # 5) compile .pyc and "level 2" .pyo (--compile --optimize-more) + # 6) compile "level 2" .pyo only (--no-compile --optimize-more) + # + # The UI for this is two option, 'compile' and 'optimize'. + # 'compile' is strictly boolean, and only decides whether to + # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and + # decides both whether to generate .pyo files and what level of + # optimization to use. + + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ('build-dir=','b', "build directory (where to install from)"), + ('force', 'f', "force installation (overwrite existing files)"), + ('compile', 'c', "compile .py to .pyc [default]"), + ('no-compile', None, "don't compile .py files"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('skip-build', None, "skip the build steps"), + ] + + boolean_options = ['force', 'compile', 'skip-build'] + negative_opt = {'no-compile' : 'compile'} + + def initialize_options(self): + # let the 'install_dist' command dictate our installation directory + self.install_dir = None + self.build_dir = None + self.force = False + self.compile = None + self.optimize = None + self.skip_build = None + + def finalize_options(self): + # Get all the information we need to install pure Python modules + # from the umbrella 'install_dist' command -- build (source) directory, + # install (target) directory, and whether to compile .py files. + self.set_undefined_options('install_dist', + ('build_lib', 'build_dir'), + ('install_lib', 'install_dir'), + 'force', 'compile', 'optimize', 'skip_build') + + if self.compile is None: + self.compile = True + if self.optimize is None: + self.optimize = 0 + + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + if self.optimize not in (0, 1, 2): + raise AssertionError + except (ValueError, AssertionError): + raise PackagingOptionError("optimize must be 0, 1, or 2") + + def run(self): + # Make sure we have built everything we need first + self.build() + + # Install everything: simply dump the entire contents of the build + # directory to the installation directory (that's the beauty of + # having a build directory!) + outfiles = self.install() + + # (Optionally) compile .py to .pyc + if outfiles is not None and self.distribution.has_pure_modules(): + self.byte_compile(outfiles) + + # -- Top-level worker functions ------------------------------------ + # (called from 'run()') + + def build(self): + if not self.skip_build: + if self.distribution.has_pure_modules(): + self.run_command('build_py') + if self.distribution.has_ext_modules(): + self.run_command('build_ext') + + def install(self): + if os.path.isdir(self.build_dir): + outfiles = self.copy_tree(self.build_dir, self.install_dir) + else: + logger.warning( + '%s: %r does not exist -- no Python modules to install', + self.get_command_name(), self.build_dir) + return + return outfiles + + def byte_compile(self, files): + if getattr(sys, 'dont_write_bytecode'): + # XXX do we want this? because a Python runs without bytecode + # doesn't mean that the *dists should not contain bytecode + #--or does it? + logger.warning('%s: byte-compiling is disabled, skipping.', + self.get_command_name()) + return + + from packaging.util import byte_compile + + # Get the "--root" directory supplied to the "install_dist" command, + # and use it as a prefix to strip off the purported filename + # encoded in bytecode files. This is far from complete, but it + # should at least generate usable bytecode in RPM distributions. + install_root = self.get_finalized_command('install_dist').root + + # Temporary kludge until we remove the verbose arguments and use + # logging everywhere + verbose = logger.getEffectiveLevel() >= logging.DEBUG + + if self.compile: + byte_compile(files, optimize=0, + force=self.force, prefix=install_root, + dry_run=self.dry_run) + if self.optimize > 0: + byte_compile(files, optimize=self.optimize, + force=self.force, prefix=install_root, + verbose=verbose, + dry_run=self.dry_run) + + + # -- Utility methods ----------------------------------------------- + + def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): + if not has_any: + return [] + + build_cmd = self.get_finalized_command(build_cmd) + build_files = build_cmd.get_outputs() + build_dir = getattr(build_cmd, cmd_option) + + prefix_len = len(build_dir) + len(os.sep) + outputs = [] + for file in build_files: + outputs.append(os.path.join(output_dir, file[prefix_len:])) + + return outputs + + def _bytecode_filenames(self, py_filenames): + bytecode_files = [] + for py_file in py_filenames: + # Since build_py handles package data installation, the + # list of outputs can contain more than just .py files. + # Make sure we only report bytecode for the .py files. + ext = os.path.splitext(os.path.normcase(py_file))[1] + if ext != PYTHON_SOURCE_EXTENSION: + continue + if self.compile: + bytecode_files.append(py_file + "c") + if self.optimize > 0: + bytecode_files.append(py_file + "o") + + return bytecode_files + + + # -- External interface -------------------------------------------- + # (called by outsiders) + + def get_outputs(self): + """Return the list of files that would be installed if this command + were actually run. Not affected by the "dry-run" flag or whether + modules have actually been built yet. + """ + pure_outputs = \ + self._mutate_outputs(self.distribution.has_pure_modules(), + 'build_py', 'build_lib', + self.install_dir) + if self.compile: + bytecode_outputs = self._bytecode_filenames(pure_outputs) + else: + bytecode_outputs = [] + + ext_outputs = \ + self._mutate_outputs(self.distribution.has_ext_modules(), + 'build_ext', 'build_lib', + self.install_dir) + + return pure_outputs + bytecode_outputs + ext_outputs + + def get_inputs(self): + """Get the list of files that are input to this command, ie. the + files that get installed as they are named in the build tree. + The files in this list correspond one-to-one to the output + filenames returned by 'get_outputs()'. + """ + inputs = [] + + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + inputs.extend(build_py.get_outputs()) + + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + inputs.extend(build_ext.get_outputs()) + + return inputs diff --git a/Lib/packaging/command/install_scripts.py b/Lib/packaging/command/install_scripts.py new file mode 100644 index 0000000000..cfacbe25fb --- /dev/null +++ b/Lib/packaging/command/install_scripts.py @@ -0,0 +1,59 @@ +"""Install scripts.""" + +# Contributed by Bastian Kleineidam + +import os +from packaging.command.cmd import Command +from packaging import logger + +class install_scripts(Command): + + description = "install scripts (Python or otherwise)" + + user_options = [ + ('install-dir=', 'd', "directory to install scripts to"), + ('build-dir=','b', "build directory (where to install from)"), + ('force', 'f', "force installation (overwrite existing files)"), + ('skip-build', None, "skip the build steps"), + ] + + boolean_options = ['force', 'skip-build'] + + + def initialize_options(self): + self.install_dir = None + self.force = False + self.build_dir = None + self.skip_build = None + + def finalize_options(self): + self.set_undefined_options('build', ('build_scripts', 'build_dir')) + self.set_undefined_options('install_dist', + ('install_scripts', 'install_dir'), + 'force', 'skip_build') + + def run(self): + if not self.skip_build: + self.run_command('build_scripts') + + if not os.path.exists(self.build_dir): + self.outfiles = [] + return + + self.outfiles = self.copy_tree(self.build_dir, self.install_dir) + if os.name == 'posix': + # Set the executable bits (owner, group, and world) on + # all the scripts we just installed. + for file in self.get_outputs(): + if self.dry_run: + logger.info("changing mode of %s", file) + else: + mode = (os.stat(file).st_mode | 0o555) & 0o7777 + logger.info("changing mode of %s to %o", file, mode) + os.chmod(file, mode) + + def get_inputs(self): + return self.distribution.scripts or [] + + def get_outputs(self): + return self.outfiles or [] diff --git a/Lib/packaging/command/register.py b/Lib/packaging/command/register.py new file mode 100644 index 0000000000..962afdcc96 --- /dev/null +++ b/Lib/packaging/command/register.py @@ -0,0 +1,282 @@ +"""Register a release with a project index.""" + +# Contributed by Richard Jones + +import io +import getpass +import urllib.error +import urllib.parse +import urllib.request + +from packaging import logger +from packaging.util import (read_pypirc, generate_pypirc, DEFAULT_REPOSITORY, + DEFAULT_REALM, get_pypirc_path) +from packaging.command.cmd import Command + +class register(Command): + + description = "register a release with PyPI" + user_options = [ + ('repository=', 'r', + "repository URL [default: %s]" % DEFAULT_REPOSITORY), + ('show-response', None, + "display full response text from server"), + ('list-classifiers', None, + "list valid Trove classifiers"), + ('strict', None , + "stop the registration if the metadata is not fully compliant") + ] + + boolean_options = ['show-response', 'list-classifiers', 'strict'] + + def initialize_options(self): + self.repository = None + self.realm = None + self.show_response = False + self.list_classifiers = False + self.strict = False + + def finalize_options(self): + if self.repository is None: + self.repository = DEFAULT_REPOSITORY + if self.realm is None: + self.realm = DEFAULT_REALM + + def run(self): + self._set_config() + + # Check the package metadata + check = self.distribution.get_command_obj('check') + if check.strict != self.strict and not check.all: + # If check was already run but with different options, + # re-run it + check.strict = self.strict + check.all = True + self.distribution.have_run.pop('check', None) + self.run_command('check') + + if self.dry_run: + self.verify_metadata() + elif self.list_classifiers: + self.classifiers() + else: + self.send_metadata() + + def _set_config(self): + ''' Reads the configuration file and set attributes. + ''' + config = read_pypirc(self.repository, self.realm) + if config != {}: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + self.has_config = True + else: + if self.repository not in ('pypi', DEFAULT_REPOSITORY): + raise ValueError('%s not found in .pypirc' % self.repository) + if self.repository == 'pypi': + self.repository = DEFAULT_REPOSITORY + self.has_config = False + + def classifiers(self): + ''' Fetch the list of classifiers from the server. + ''' + response = urllib.request.urlopen(self.repository+'?:action=list_classifiers') + logger.info(response.read()) + + def verify_metadata(self): + ''' Send the metadata to the package index server to be checked. + ''' + # send the info to the server and report the result + code, result = self.post_to_server(self.build_post_data('verify')) + logger.info('server response (%s): %s', code, result) + + + def send_metadata(self): + ''' Send the metadata to the package index server. + + Well, do the following: + 1. figure who the user is, and then + 2. send the data as a Basic auth'ed POST. + + First we try to read the username/password from $HOME/.pypirc, + which is a ConfigParser-formatted file with a section + [distutils] containing username and password entries (both + in clear text). Eg: + + [distutils] + index-servers = + pypi + + [pypi] + username: fred + password: sekrit + + Otherwise, to figure who the user is, we offer the user three + choices: + + 1. use existing login, + 2. register as a new user, or + 3. set the password to a random string and email the user. + + ''' + # TODO factor registration out into another method + # TODO use print to print, not logging + + # see if we can short-cut and get the username/password from the + # config + if self.has_config: + choice = '1' + username = self.username + password = self.password + else: + choice = 'x' + username = password = '' + + # get the user's login info + choices = '1 2 3 4'.split() + while choice not in choices: + logger.info('''\ +We need to know who you are, so please choose either: + 1. use your existing login, + 2. register as a new user, + 3. have the server generate a new password for you (and email it to you), or + 4. quit +Your selection [default 1]: ''') + + choice = input() + if not choice: + choice = '1' + elif choice not in choices: + print('Please choose one of the four options!') + + if choice == '1': + # get the username and password + while not username: + username = input('Username: ') + while not password: + password = getpass.getpass('Password: ') + + # set up the authentication + auth = urllib.request.HTTPPasswordMgr() + host = urllib.parse.urlparse(self.repository)[1] + auth.add_password(self.realm, host, username, password) + # send the info to the server and report the result + code, result = self.post_to_server(self.build_post_data('submit'), + auth) + logger.info('Server response (%s): %s', code, result) + + # possibly save the login + if code == 200: + if self.has_config: + # sharing the password in the distribution instance + # so the upload command can reuse it + self.distribution.password = password + else: + logger.info( + 'I can store your PyPI login so future submissions ' + 'will be faster.\n(the login will be stored in %s)', + get_pypirc_path()) + choice = 'X' + while choice.lower() not in 'yn': + choice = input('Save your login (y/N)?') + if not choice: + choice = 'n' + if choice.lower() == 'y': + generate_pypirc(username, password) + + elif choice == '2': + data = {':action': 'user'} + data['name'] = data['password'] = data['email'] = '' + data['confirm'] = None + while not data['name']: + data['name'] = input('Username: ') + while data['password'] != data['confirm']: + while not data['password']: + data['password'] = getpass.getpass('Password: ') + while not data['confirm']: + data['confirm'] = getpass.getpass(' Confirm: ') + if data['password'] != data['confirm']: + data['password'] = '' + data['confirm'] = None + print("Password and confirm don't match!") + while not data['email']: + data['email'] = input(' EMail: ') + code, result = self.post_to_server(data) + if code != 200: + logger.info('server response (%s): %s', code, result) + else: + logger.info('you will receive an email shortly; follow the ' + 'instructions in it to complete registration.') + elif choice == '3': + data = {':action': 'password_reset'} + data['email'] = '' + while not data['email']: + data['email'] = input('Your email address: ') + code, result = self.post_to_server(data) + logger.info('server response (%s): %s', code, result) + + def build_post_data(self, action): + # figure the data to send - the metadata plus some additional + # information used by the package server + data = self.distribution.metadata.todict() + data[':action'] = action + return data + + # XXX to be refactored with upload.upload_file + def post_to_server(self, data, auth=None): + ''' Post a query to the server, and return a string response. + ''' + if 'name' in data: + logger.info('Registering %s to %s', data['name'], self.repository) + # Build up the MIME payload for the urllib2 POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = '\n--' + boundary + end_boundary = sep_boundary + '--' + body = io.StringIO() + for key, value in data.items(): + # handle multiple entries for the same name + if not isinstance(value, (tuple, list)): + value = [value] + + for value in value: + body.write(sep_boundary) + body.write('\nContent-Disposition: form-data; name="%s"'%key) + body.write("\n\n") + body.write(value) + if value and value[-1] == '\r': + body.write('\n') # write an extra newline (lurve Macs) + body.write(end_boundary) + body.write("\n") + body = body.getvalue() + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, + 'Content-length': str(len(body)) + } + req = urllib.request.Request(self.repository, body, headers) + + # handle HTTP and include the Basic Auth handler + opener = urllib.request.build_opener( + urllib.request.HTTPBasicAuthHandler(password_mgr=auth) + ) + data = '' + try: + result = opener.open(req) + except urllib.error.HTTPError as e: + if self.show_response: + data = e.fp.read() + result = e.code, e.msg + except urllib.error.URLError as e: + result = 500, str(e) + else: + if self.show_response: + data = result.read() + result = 200, 'OK' + if self.show_response: + dashes = '-' * 75 + logger.info('%s%s%s', dashes, data, dashes) + + return result diff --git a/Lib/packaging/command/sdist.py b/Lib/packaging/command/sdist.py new file mode 100644 index 0000000000..a28019b366 --- /dev/null +++ b/Lib/packaging/command/sdist.py @@ -0,0 +1,375 @@ +"""Create a source distribution.""" + +import os +import sys +import re +from io import StringIO +from glob import glob +from shutil import get_archive_formats, rmtree + +from packaging import logger +from packaging.util import resolve_name +from packaging.errors import (PackagingPlatformError, PackagingOptionError, + PackagingModuleError, PackagingFileError) +from packaging.command import get_command_names +from packaging.command.cmd import Command +from packaging.manifest import Manifest + + +def show_formats(): + """Print all possible values for the 'formats' option (used by + the "--help-formats" command-line option). + """ + from packaging.fancy_getopt import FancyGetopt + formats = sorted(('formats=' + name, None, desc) + for name, desc in get_archive_formats()) + FancyGetopt(formats).print_help( + "List of available source distribution formats:") + +# a \ followed by some spaces + EOL +_COLLAPSE_PATTERN = re.compile('\\\w\n', re.M) +_COMMENTED_LINE = re.compile('^#.*\n$|^\w*\n$', re.M) + + +class sdist(Command): + + description = "create a source distribution (tarball, zip file, etc.)" + + user_options = [ + ('manifest=', 'm', + "name of manifest file [default: MANIFEST]"), + ('use-defaults', None, + "include the default file set in the manifest " + "[default; disable with --no-defaults]"), + ('no-defaults', None, + "don't include the default file set"), + ('prune', None, + "specifically exclude files/directories that should not be " + "distributed (build tree, RCS/CVS dirs, etc.) " + "[default; disable with --no-prune]"), + ('no-prune', None, + "don't automatically exclude anything"), + ('manifest-only', 'o', + "just regenerate the manifest and then stop "), + ('formats=', None, + "formats for source distribution (comma-separated list)"), + ('keep-temp', 'k', + "keep the distribution tree around after creating " + + "archive file(s)"), + ('dist-dir=', 'd', + "directory to put the source distribution archive(s) in " + "[default: dist]"), + ('check-metadata', None, + "Ensure that all required elements of metadata " + "are supplied. Warn if any missing. [default]"), + ('owner=', 'u', + "Owner name used when creating a tar file [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file [default: current group]"), + ('manifest-builders=', None, + "manifest builders (comma-separated list)"), + ] + + boolean_options = ['use-defaults', 'prune', + 'manifest-only', 'keep-temp', 'check-metadata'] + + help_options = [ + ('help-formats', None, + "list available distribution formats", show_formats), + ] + + negative_opt = {'no-defaults': 'use-defaults', + 'no-prune': 'prune'} + + default_format = {'posix': 'gztar', + 'nt': 'zip'} + + def initialize_options(self): + self.manifest = None + # 'use_defaults': if true, we will include the default file set + # in the manifest + self.use_defaults = True + self.prune = True + self.manifest_only = False + self.formats = None + self.keep_temp = False + self.dist_dir = None + + self.archive_files = None + self.metadata_check = True + self.owner = None + self.group = None + self.filelist = None + self.manifest_builders = None + + def _check_archive_formats(self, formats): + supported_formats = [name for name, desc in get_archive_formats()] + for format in formats: + if format not in supported_formats: + return format + return None + + def finalize_options(self): + if self.manifest is None: + self.manifest = "MANIFEST" + + self.ensure_string_list('formats') + if self.formats is None: + try: + self.formats = [self.default_format[os.name]] + except KeyError: + raise PackagingPlatformError("don't know how to create source " + "distributions on platform %s" % os.name) + + bad_format = self._check_archive_formats(self.formats) + if bad_format: + raise PackagingOptionError("unknown archive format '%s'" \ + % bad_format) + + if self.dist_dir is None: + self.dist_dir = "dist" + + if self.filelist is None: + self.filelist = Manifest() + + if self.manifest_builders is None: + self.manifest_builders = [] + else: + if isinstance(self.manifest_builders, str): + self.manifest_builders = self.manifest_builders.split(',') + builders = [] + for builder in self.manifest_builders: + builder = builder.strip() + if builder == '': + continue + try: + builder = resolve_name(builder) + except ImportError as e: + raise PackagingModuleError(e) + + builders.append(builder) + + self.manifest_builders = builders + + def run(self): + # 'filelist' contains the list of files that will make up the + # manifest + self.filelist.clear() + + # Check the package metadata + if self.metadata_check: + self.run_command('check') + + # Do whatever it takes to get the list of files to process + # (process the manifest template, read an existing manifest, + # whatever). File list is accumulated in 'self.filelist'. + self.get_file_list() + + # If user just wanted us to regenerate the manifest, stop now. + if self.manifest_only: + return + + # Otherwise, go ahead and create the source distribution tarball, + # or zipfile, or whatever. + self.make_distribution() + + def get_file_list(self): + """Figure out the list of files to include in the source + distribution, and put it in 'self.filelist'. This might involve + reading the manifest template (and writing the manifest), or just + reading the manifest, or just using the default file set -- it all + depends on the user's options. + """ + template_exists = len(self.distribution.extra_files) > 0 + if not template_exists: + logger.warning('%s: using default file list', + self.get_command_name()) + self.filelist.findall() + + if self.use_defaults: + self.add_defaults() + if template_exists: + template = '\n'.join(self.distribution.extra_files) + self.filelist.read_template(StringIO(template)) + + # call manifest builders, if any. + for builder in self.manifest_builders: + builder(self.distribution, self.filelist) + + if self.prune: + self.prune_file_list() + + self.filelist.write(self.manifest) + + def add_defaults(self): + """Add all the default files to self.filelist: + - README or README.txt + - test/test*.py + - all pure Python modules mentioned in setup script + - all files pointed by package_data (build_py) + - all files defined in data_files. + - all files defined as scripts. + - all C sources listed as part of extensions or C libraries + in the setup script (doesn't catch C headers!) + Warns if (README or README.txt) or setup.py are missing; everything + else is optional. + """ + standards = [('README', 'README.txt')] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = False + for fn in alts: + if os.path.exists(fn): + got_it = True + self.filelist.append(fn) + break + + if not got_it: + logger.warning( + '%s: standard file not found: should have one of %s', + self.get_command_name(), ', '.join(alts)) + else: + if os.path.exists(fn): + self.filelist.append(fn) + else: + logger.warning('%s: standard file %r not found', + self.get_command_name(), fn) + + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = [f for f in glob(pattern) if os.path.isfile(f)] + if files: + self.filelist.extend(files) + + for cmd_name in get_command_names(): + try: + cmd_obj = self.get_finalized_command(cmd_name) + except PackagingOptionError: + pass + else: + self.filelist.extend(cmd_obj.get_source_files()) + + def prune_file_list(self): + """Prune off branches that might slip into the file list as created + by 'read_template()', but really don't belong there: + * the build tree (typically "build") + * the release tree itself (only an issue if we ran "sdist" + previously with --keep-temp, or it aborted) + * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories + """ + build = self.get_finalized_command('build') + base_dir = self.distribution.get_fullname() + + self.filelist.exclude_pattern(None, prefix=build.build_base) + self.filelist.exclude_pattern(None, prefix=base_dir) + + # pruning out vcs directories + # both separators are used under win32 + if sys.platform == 'win32': + seps = r'/|\\' + else: + seps = '/' + + vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', + '_darcs'] + vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) + self.filelist.exclude_pattern(vcs_ptrn, is_regex=True) + + def make_release_tree(self, base_dir, files): + """Create the directory tree that will become the source + distribution archive. All directories implied by the filenames in + 'files' are created under 'base_dir', and then we hard link or copy + (if hard linking is unavailable) those files into place. + Essentially, this duplicates the developer's source tree, but in a + directory named after the distribution, containing only the files + to be distributed. + """ + # Create all the directories under 'base_dir' necessary to + # put 'files' there; the 'mkpath()' is just so we don't die + # if the manifest happens to be empty. + self.mkpath(base_dir) + self.create_tree(base_dir, files, dry_run=self.dry_run) + + # And walk over the list of files, either making a hard link (if + # os.link exists) to each one that doesn't already exist in its + # corresponding location under 'base_dir', or copying each file + # that's out-of-date in 'base_dir'. (Usually, all files will be + # out-of-date, because by default we blow away 'base_dir' when + # we're done making the distribution archives.) + + if hasattr(os, 'link'): # can make hard links on this system + link = 'hard' + msg = "making hard links in %s..." % base_dir + else: # nope, have to copy + link = None + msg = "copying files to %s..." % base_dir + + if not files: + logger.warning("no files to distribute -- empty manifest?") + else: + logger.info(msg) + + for file in self.distribution.metadata.requires_files: + if file not in files: + msg = "'%s' must be included explicitly in 'extra_files'" \ + % file + raise PackagingFileError(msg) + + for file in files: + if not os.path.isfile(file): + logger.warning("'%s' not a regular file -- skipping", file) + else: + dest = os.path.join(base_dir, file) + self.copy_file(file, dest, link=link) + + self.distribution.metadata.write(os.path.join(base_dir, 'PKG-INFO')) + + def make_distribution(self): + """Create the source distribution(s). First, we create the release + tree with 'make_release_tree()'; then, we create all required + archive files (according to 'self.formats') from the release tree. + Finally, we clean up by blowing away the release tree (unless + 'self.keep_temp' is true). The list of archive files created is + stored so it can be retrieved later by 'get_archive_files()'. + """ + # Don't warn about missing metadata here -- should be (and is!) + # done elsewhere. + base_dir = self.distribution.get_fullname() + base_name = os.path.join(self.dist_dir, base_dir) + + self.make_release_tree(base_dir, self.filelist.files) + archive_files = [] # remember names of files we create + # tar archive must be created last to avoid overwrite and remove + if 'tar' in self.formats: + self.formats.append(self.formats.pop(self.formats.index('tar'))) + + for fmt in self.formats: + file = self.make_archive(base_name, fmt, base_dir=base_dir, + owner=self.owner, group=self.group) + archive_files.append(file) + self.distribution.dist_files.append(('sdist', '', file)) + + self.archive_files = archive_files + + if not self.keep_temp: + if self.dry_run: + logger.info('removing %s', base_dir) + else: + rmtree(base_dir) + + def get_archive_files(self): + """Return the list of archive files created when the command + was run, or None if the command hasn't run yet. + """ + return self.archive_files + + def create_tree(self, base_dir, files, mode=0o777, verbose=1, + dry_run=False): + need_dir = set() + for file in files: + need_dir.add(os.path.join(base_dir, os.path.dirname(file))) + + # Now create them + for dir in sorted(need_dir): + self.mkpath(dir, mode, verbose=verbose, dry_run=dry_run) diff --git a/Lib/packaging/command/test.py b/Lib/packaging/command/test.py new file mode 100644 index 0000000000..7f9015bc8e --- /dev/null +++ b/Lib/packaging/command/test.py @@ -0,0 +1,81 @@ +"""Run the project's test suite.""" + +import os +import sys +import logging +import unittest + +from packaging import logger +from packaging.command.cmd import Command +from packaging.database import get_distribution +from packaging.errors import PackagingOptionError +from packaging.util import resolve_name + + +class test(Command): + + description = "run the project's test suite" + + user_options = [ + ('suite=', 's', + "test suite to run (for example: 'some_module.test_suite')"), + ('runner=', None, + "test runner to be called."), + ('tests-require=', None, + "list of distributions required to run the test suite."), + ] + + def initialize_options(self): + self.suite = None + self.runner = None + self.tests_require = [] + + def finalize_options(self): + self.build_lib = self.get_finalized_command("build").build_lib + for requirement in self.tests_require: + if get_distribution(requirement) is None: + logger.warning("test dependency %s is not installed, " + "tests may fail", requirement) + if (not self.suite and not self.runner and + self.get_ut_with_discovery() is None): + raise PackagingOptionError( + "no test discovery available, please give a 'suite' or " + "'runner' option or install unittest2") + + def get_ut_with_discovery(self): + if hasattr(unittest.TestLoader, "discover"): + return unittest + else: + try: + import unittest2 + return unittest2 + except ImportError: + return None + + def run(self): + prev_syspath = sys.path[:] + try: + # build release + build = self.get_reinitialized_command('build') + self.run_command('build') + sys.path.insert(0, build.build_lib) + + # Temporary kludge until we remove the verbose arguments and use + # logging everywhere + logger = logging.getLogger('packaging') + verbose = logger.getEffectiveLevel() >= logging.DEBUG + verbosity = verbose + 1 + + # run the tests + if self.runner: + resolve_name(self.runner)() + elif self.suite: + runner = unittest.TextTestRunner(verbosity=verbosity) + runner.run(resolve_name(self.suite)()) + elif self.get_ut_with_discovery(): + ut = self.get_ut_with_discovery() + test_suite = ut.TestLoader().discover(os.curdir) + runner = ut.TextTestRunner(verbosity=verbosity) + runner.run(test_suite) + finally: + sys.path[:] = prev_syspath diff --git a/Lib/packaging/command/upload.py b/Lib/packaging/command/upload.py new file mode 100644 index 0000000000..df265c9591 --- /dev/null +++ b/Lib/packaging/command/upload.py @@ -0,0 +1,201 @@ +"""Upload a distribution to a project index.""" + +import os +import socket +import logging +import platform +import urllib.parse +from io import BytesIO +from base64 import standard_b64encode +from hashlib import md5 +from urllib.error import HTTPError +from urllib.request import urlopen, Request + +from packaging import logger +from packaging.errors import PackagingOptionError +from packaging.util import (spawn, read_pypirc, DEFAULT_REPOSITORY, + DEFAULT_REALM) +from packaging.command.cmd import Command + + +class upload(Command): + + description = "upload distribution to PyPI" + + user_options = [ + ('repository=', 'r', + "repository URL [default: %s]" % DEFAULT_REPOSITORY), + ('show-response', None, + "display full response text from server"), + ('sign', 's', + "sign files to upload using gpg"), + ('identity=', 'i', + "GPG identity used to sign files"), + ('upload-docs', None, + "upload documentation too"), + ] + + boolean_options = ['show-response', 'sign'] + + def initialize_options(self): + self.repository = None + self.realm = None + self.show_response = False + self.username = '' + self.password = '' + self.show_response = False + self.sign = False + self.identity = None + self.upload_docs = False + + def finalize_options(self): + if self.repository is None: + self.repository = DEFAULT_REPOSITORY + if self.realm is None: + self.realm = DEFAULT_REALM + if self.identity and not self.sign: + raise PackagingOptionError( + "Must use --sign for --identity to have meaning") + config = read_pypirc(self.repository, self.realm) + if config != {}: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + + # getting the password from the distribution + # if previously set by the register command + if not self.password and self.distribution.password: + self.password = self.distribution.password + + def run(self): + if not self.distribution.dist_files: + raise PackagingOptionError( + "No dist file created in earlier command") + for command, pyversion, filename in self.distribution.dist_files: + self.upload_file(command, pyversion, filename) + if self.upload_docs: + upload_docs = self.get_finalized_command("upload_docs") + upload_docs.repository = self.repository + upload_docs.username = self.username + upload_docs.password = self.password + upload_docs.run() + + # XXX to be refactored with register.post_to_server + def upload_file(self, command, pyversion, filename): + # Makes sure the repository URL is compliant + scheme, netloc, url, params, query, fragments = \ + urllib.parse.urlparse(self.repository) + if params or query or fragments: + raise AssertionError("Incompatible url %s" % self.repository) + + if scheme not in ('http', 'https'): + raise AssertionError("unsupported scheme " + scheme) + + # Sign if requested + if self.sign: + gpg_args = ["gpg", "--detach-sign", "-a", filename] + if self.identity: + gpg_args[2:2] = ["--local-user", self.identity] + spawn(gpg_args, + dry_run=self.dry_run) + + # Fill in the data - send all the metadata in case we need to + # register a new release + with open(filename, 'rb') as f: + content = f.read() + + data = self.distribution.metadata.todict() + + # extra upload infos + data[':action'] = 'file_upload' + data['protcol_version'] = '1' + data['content'] = (os.path.basename(filename), content) + data['filetype'] = command + data['pyversion'] = pyversion + data['md5_digest'] = md5(content).hexdigest() + + if command == 'bdist_dumb': + data['comment'] = 'built for %s' % platform.platform(terse=True) + + if self.sign: + with open(filename + '.asc') as fp: + sig = fp.read() + data['gpg_signature'] = [ + (os.path.basename(filename) + ".asc", sig)] + + # set up the authentication + # The exact encoding of the authentication string is debated. + # Anyway PyPI only accepts ascii for both username or password. + user_pass = (self.username + ":" + self.password).encode('ascii') + auth = b"Basic " + standard_b64encode(user_pass) + + # Build up the MIME payload for the POST data + boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\n--' + boundary + end_boundary = sep_boundary + b'--' + body = BytesIO() + + file_fields = ('content', 'gpg_signature') + + for key, value in data.items(): + # handle multiple entries for the same name + if not isinstance(value, tuple): + value = [value] + + content_dispo = '\nContent-Disposition: form-data; name="%s"' % key + + if key in file_fields: + filename_, content = value + filename_ = ';filename="%s"' % filename_ + body.write(sep_boundary) + body.write(content_dispo.encode('utf-8')) + body.write(filename_.encode('utf-8')) + body.write(b"\n\n") + body.write(content) + else: + for value in value: + value = str(value).encode('utf-8') + body.write(sep_boundary) + body.write(content_dispo.encode('utf-8')) + body.write(b"\n\n") + body.write(value) + if value and value.endswith(b'\r'): + # write an extra newline (lurve Macs) + body.write(b'\n') + + body.write(end_boundary) + body.write(b"\n") + body = body.getvalue() + + logger.info("Submitting %s to %s", filename, self.repository) + + # build the Request + headers = {'Content-type': + 'multipart/form-data; boundary=%s' % + boundary.decode('ascii'), + 'Content-length': str(len(body)), + 'Authorization': auth} + + request = Request(self.repository, data=body, + headers=headers) + # send the data + try: + result = urlopen(request) + status = result.code + reason = result.msg + except socket.error as e: + logger.error(e) + return + except HTTPError as e: + status = e.code + reason = e.msg + + if status == 200: + logger.info('Server response (%s): %s', status, reason) + else: + logger.error('Upload failed (%s): %s', status, reason) + + if self.show_response and logger.isEnabledFor(logging.INFO): + sep = '-' * 75 + logger.info('%s\n%s\n%s', sep, result.read().decode(), sep) diff --git a/Lib/packaging/command/upload_docs.py b/Lib/packaging/command/upload_docs.py new file mode 100644 index 0000000000..29ea6e92e8 --- /dev/null +++ b/Lib/packaging/command/upload_docs.py @@ -0,0 +1,173 @@ +"""Upload HTML documentation to a project index.""" + +import os +import base64 +import socket +import zipfile +import logging +import http.client +import urllib.parse +from io import BytesIO + +from packaging import logger +from packaging.util import read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM +from packaging.errors import PackagingFileError +from packaging.command.cmd import Command + + +def zip_dir(directory): + """Compresses recursively contents of directory into a BytesIO object""" + destination = BytesIO() + zip_file = zipfile.ZipFile(destination, "w") + for root, dirs, files in os.walk(directory): + for name in files: + full = os.path.join(root, name) + relative = root[len(directory):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + zip_file.close() + return destination + + +# grabbed from +# http://code.activestate.com/recipes/ +# 146306-http-client-to-post-using-multipartform-data/ +# TODO factor this out for use by install and command/upload + +def encode_multipart(fields, files, boundary=None): + """ + *fields* is a sequence of (name: str, value: str) elements for regular + form fields, *files* is a sequence of (name: str, filename: str, value: + bytes) elements for data to be uploaded as files. + + Returns (content_type: bytes, body: bytes) ready for http.client.HTTP. + """ + if boundary is None: + boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + elif not isinstance(boundary, bytes): + raise TypeError('boundary is not bytes but %r' % type(boundary)) + + l = [] + for key, value in fields: + l.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"' % + key).encode('utf-8'), + b'', + value.encode('utf-8'))) + + for key, filename, value in files: + l.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"; filename="%s"' % + (key, filename)).encode('utf-8'), + b'', + value)) + l.append(b'--' + boundary + b'--') + l.append(b'') + + body = b'\r\n'.join(l) + + content_type = b'multipart/form-data; boundary=' + boundary + return content_type, body + + +class upload_docs(Command): + + description = "upload HTML documentation to PyPI" + + user_options = [ + ('repository=', 'r', + "repository URL [default: %s]" % DEFAULT_REPOSITORY), + ('show-response', None, + "display full response text from server"), + ('upload-dir=', None, + "directory to upload"), + ] + + def initialize_options(self): + self.repository = None + self.realm = None + self.show_response = False + self.upload_dir = None + self.username = '' + self.password = '' + + def finalize_options(self): + if self.repository is None: + self.repository = DEFAULT_REPOSITORY + if self.realm is None: + self.realm = DEFAULT_REALM + if self.upload_dir is None: + build = self.get_finalized_command('build') + self.upload_dir = os.path.join(build.build_base, "docs") + if not os.path.isdir(self.upload_dir): + self.upload_dir = os.path.join(build.build_base, "doc") + logger.info('Using upload directory %s', self.upload_dir) + self.verify_upload_dir(self.upload_dir) + config = read_pypirc(self.repository, self.realm) + if config != {}: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + + def verify_upload_dir(self, upload_dir): + self.ensure_dirname('upload_dir') + index_location = os.path.join(upload_dir, "index.html") + if not os.path.exists(index_location): + mesg = "No 'index.html found in docs directory (%s)" + raise PackagingFileError(mesg % upload_dir) + + def run(self): + name = self.distribution.metadata['Name'] + version = self.distribution.metadata['Version'] + zip_file = zip_dir(self.upload_dir) + + fields = [(':action', 'doc_upload'), + ('name', name), ('version', version)] + files = [('content', name, zip_file.getvalue())] + content_type, body = encode_multipart(fields, files) + + credentials = self.username + ':' + self.password + auth = b"Basic " + base64.encodebytes(credentials.encode()).strip() + + logger.info("Submitting documentation to %s", self.repository) + + scheme, netloc, url, params, query, fragments = urllib.parse.urlparse( + self.repository) + if scheme == "http": + conn = http.client.HTTPConnection(netloc) + elif scheme == "https": + conn = http.client.HTTPSConnection(netloc) + else: + raise AssertionError("unsupported scheme %r" % scheme) + + try: + conn.connect() + conn.putrequest("POST", url) + conn.putheader('Content-type', content_type) + conn.putheader('Content-length', str(len(body))) + conn.putheader('Authorization', auth) + conn.endheaders() + conn.send(body) + + except socket.error as e: + logger.error(e) + return + + r = conn.getresponse() + + if r.status == 200: + logger.info('Server response (%s): %s', r.status, r.reason) + elif r.status == 301: + location = r.getheader('Location') + if location is None: + location = 'http://packages.python.org/%s/' % name + logger.info('Upload successful. Visit %s', location) + else: + logger.error('Upload failed (%s): %s', r.status, r.reason) + + if self.show_response and logger.isEnabledFor(logging.INFO): + sep = '-' * 75 + logger.info('%s\n%s\n%s', sep, r.read().decode('utf-8'), sep) diff --git a/Lib/packaging/command/wininst-10.0-amd64.exe b/Lib/packaging/command/wininst-10.0-amd64.exe new file mode 100644 index 0000000000000000000000000000000000000000..11f98cd2adf1075b7ff7be7f02ebc8e743bb6b9e GIT binary patch literal 222208 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 Hh=4Kxkw}Vy literal 0 HcmV?d00001 diff --git a/Lib/packaging/command/wininst-6.0.exe b/Lib/packaging/command/wininst-6.0.exe new file mode 100644 index 0000000000000000000000000000000000000000..f57c855a613e2de2b00fff1df431b8d08d171077 GIT binary patch literal 61440 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�B&N2z=n5CGeG0Sy{>DZ17F}S18960H2)16rR#NF z0~%ch750$vdr-&E_yA~<5tQLUVF{WG19xdavn;)>IsgCvKMp2A{DA+W8IT%Tta)$2 z|NsA4vlt`Fc}h7f!9A5?CXiWR-JljHsOEzVeuDbS86EpTQ{n$bpMVnG4~2TQfL_;z z3?GQsKtp+8mo{VxWVnFDKy&4w39gfeq4Pgx;Bm0<|E3zZ92mN7O9~tq_JWH&7Zs7v z-JrqcfZ%-~tr|rR;%Gd$cYY)|1BALaIv0TN!@>doMRP!W=%flebOI1I&J2pw!=c@8x?Op~S`UPmd>euDL=+N-fpvJ4=r*WNJ!_T}Do`#=ArPdC|*m&Ju8-uB1 zY+P>s{;$=0eL1o?0t5bw-T@`PBRn=-rK})j{vB-Ht{iUuuUR|z+#LK{50uV)&D>qe z(V)f<9|vBfvI8`R`ohBm+`s=8-fasial($f-T-YmJnnjjS=xaCydvYl|LYkH^$ZMQ zovtrBOW*tlw<|Ij7#bKlT|v{y1)v7SmtNN!yLU5iedMp_yKqBC7G{nIDdq4t^VGf8$ zK*j=)2TT8e#&|%cKnFpZks8@qF1@ZR;=+OhUaU6)bq)l2UEhF<$t$3BlL5W1D+0j7 zGr?iO!4Ss<2Lz{e#@_fZ+5;(_oV!gQfU@XE&=~x+EdKwZEuez`|FsNIx%6MO0n8Jv z0ZmhvzUejvEldQhTKMo^v;r*l;r}&Q7=cCcfx8=^*(a27 z5lVbvi||DeCB9%m^~Dbod=Zvq1)25+#Q?ZX9$Cs1cX**dp#y_kvjtE&Mp?m<_8?D2TCu5K?O_G875)H}>&cPW5haDI? zV^l<5oY9Ar>HIB`j0_B+;o-+!R8&+BI52e9sAzP?s3<_YN#GI2?ib+ZBY2b%)WHBv zB{UxaHT*zbA3U9;P*7)zrxiyR>3@le$bV7L;45hLbcrcw8Zsb@DLCv!%@@#6uz)QC zXj&gf?*`iW0lSRcPEIJKivyk@wFw9Ja^U@&a8ScN^gp<7(~u$)!T{>v)TpR1#Dc1+ zUKbUW41ojSb`w%=0;M2uAiqe}1I^}ux;AmqjYmLn2dZ;+fF`zHMCySeG2Hlnb1esR zXYGg1+Ap28Z#rxL9C!V}1zJ168y=ova>#)p{G|a|{1j)06Fuoa*Q1M9=3-Vm7#(BNUf3xBB2 zQYDC8pd}nHr9cZWKyj1B@Z$STP~+z19!3U+7q{<#T3Y-qN}$CeU%{)~K!FedwxkBx z5~J`J?Y}{aBTG2`yS{k|T7?7|>bE`&=^i%!VCHYx23v1U8{5_SR>5NjI|79GI;53>iBPpbguA#si8# zie3Le0wmzYlpCNRZm#{qP@?d^^bf=V|4ZNeFa45{0?F5qPJ_s6>Hno~VDeuw9FQdi zvK$~X89E?gSf}E}|8M{QgJvE_R6AuAeRf=1jkKvTFGptf!XXfzQrR~zsGlrq3& zatZqjtWLbo4>^{Eox?rYj% zEZP@4MM3N4wB1;`eFeJzfxA)KZXBJA+I1}O5*E~2gDgb_tr-abEBY_G0Mc&E4F-*U zgCsKUKmvCTL?XoaKtMpi|1y?<;4G#W@@GJ`pG>DKN9%zSpZ{en;Q^54o&QTYUa)I0 zF!a{2GloOT&u(9d<|87Y(No55SCLK$$bJt{dKb;ugGf0qmR<#U4w@v-AcO7@6&B-@ z))z~ByPagL50+xLFf=eQbb91;dIWTO_!u8JyyM^h|Njr4IdcY-^23e)TOWs%JPj6x z{H-e(7#NysR75QJTjzq-=76?XS%2qm0S!f2)+uz>NtALOcT?C9>%h=ir(s#AQp$eZ zP31zY14Fl)MuveZXh})H|1i*29qZ$z9C159E0E(4cl)RaSfB57lc{Cztl;>5^R;02 z#e=_CoSJ_cbOvzLGB-cuZ~nVtY~Xn*fM zW__?otGkq^yV^wiWB0*>FPT0s?LN``koohHZf}kFxY*;)pphqrPH!FK1JQ@Iy$!5S zbo!`>l(BV#>F!{I?rM|nQXb}C-4_qOWYYfdd1?14h<+ax5$%H|8W7XFPjp|@{s1u_ zA|K!BY|!nj5!dOg16q-FxVsv}W&YWH-TFdjwLvL6)aveF9qS9ltjrfXgLNRCYDDOO z~*lyY}_u(Y1!Z%qX8t+`9t zjx+OshBG~Qx&s74yR&V=x(&j+D_FXnEjoQvG`hK)f68=*s3>%ns7Q3ysEB}8)^*mX z*mQ=d7#w#|0S$;TlnFwjEiN{?`H02g?hqA)|0=y#VjLK{y*XM>mU2Vvcv&LMz|j0x zpjcx<>wo^1Bf<;}{M*F3&t87y-+YtiGaWtrVz>IBNClCDgwtr8z&gL zkG}jP#K6$)tk8PkxHI@HnG*Kn4hH}K|NsBZnS+tP1+?54l#06#s{UtSU^w`I6PUR!tmtl<=r^V0f7!#K2(u7Md1ALqj`*MPB&GF)(z8 zsK|8ts6-rm#qs@8^CPC_BRrkX5}l3)&8Ij!g+U(bKKjxSWDmq<1CY%wDgrOXg&@JI z4hq#gA%dZ5!_nz1P$B?w*KtR%2VPzgWMKGqAu1XjE-ISc&JsZZFSaWpd=Byv$oHT@;BIe$Z%0{5%0N!h?FCKg zGk`+khyY4R;B-%?vqK4Ur=tiK7odl|DTWJP^FjQ?e%uk9^p7*Mf^s|9MbMP*?O=Vr z(?><7jIA3?gGvikbz9c8sY+w%bKT1{7gofiKz=7#O;JR5UuB zB|xIhM|iq}9khQkf7HGR3XM)skrV9D?dF! zmhdw$7@vfMFaI_XLr0LOz`pMcQPC;U?GBWIdPJ!^*ugqbp;)ThN5ulB4d5NXgxR%nMx$VnbcV#C@kQ`33+fy1arBMiVVo< z5REYB^S9pPV_-mv1R2n_Xt+1nVcrnnZ<)ow08SQb`9R*l?g14P4`@*60d}H1AdBh& z@Cj+hnK@uCXDaarrE756^NE*;6fM#C5OkuO^+9-H!vHF5f_OotBDjoAC(A50SS$QqdnKIAy~LW21e^FdHC39-f)yrdtLt3l%h z-QM7Obq5b<#u?CFOSG5a?EWG*lM zuhQ!g>%egE1=De6UZ}CnZ<${H;s$MhU^?#XBT($XaNIeB6EwZ$93W5);zfYUgyYPh zbKXGZ7D&TMs0L&KVW5GKi%xU(0$_jB}f7}_)5U~6ug!?Q{%w!QjeQ~!T3O@w*$yRP(1K~Q=voa zNl;ejZ&}2~z+inIR*r$1A=anL*t$=FWI?4`5XgNcDiYl_DjMK+8mOS^4pEVSwbW#~ zK?^WzR1~y7f|p{JsAw=>>kM{yIhP9*ZG6Sbpi<}^7j|Fr zfqe*$HTlMapiL+TU$Ayx==4qi(I>zy2A1YW@}1r>-QEdK2Vbx>zvXNEDaXjb&>5Tp z@-TlpXs){ZRP#?c{&rAT>bNtgtH*Gh8C0@&duLdC#}r3H%D?8n%n;|q<8Tfv5d~-P zZx;!jQ0>5Ae6s7r!57TUZ<#@sH9ure>+}gO==RQV>;xNKw7T0nV}~$ka{NTGmhs8g z+`UV{g#jpjyAkFl_jgc$@)L z{$&aGIv0R8UPlNpFo0TaE-EIi2S7D{i2^9^9e0%20E&D=PI%($0r3?%LG=MN1O&ke zA)z}sh54doa0Y*~5NLI>cL=nV)$Q%$rF@~AXCnW$tJYULeN-IEY(SCa$O(_EZtoCK zE0p;VLW>)y6i3zvs*ikB9LkS$hp0Fld?k?9{7axXh=1Ez>zAM;R;&gx^DPI$Odk~) zh@o!QFF}TZ)4;8??hq9xP}cAE&PeMF@rDGli;6&}cg9N(Mh1q`4E}9rdjpxQUxAI) z?*0!lcnVg7AzmB`8=zywA z=J(yzDc!*t%+(3Z7p<>@ebzn$R9&7dk?0Ol(dqu*T@4D!>Vod-40q4Y>WtENpiJ5w zoY3u^!hFioJA=RJ9cbsiwKRXrJ2nOetckKaI02e2L8W65I9W2jI0&jvj!1y_bD5NY z8V0b|d;usK&SHZn%McL13&aPt_-j-|jyp<##mYcp!8xEj5F7x{0TBU#-QEeu8Nh8B z=ahin5buJZET$I$C>Dx9`x!Ph4h*17@lq3Hm0d-u0It@B2SOPk|EjqJpUes_ffJ_VN26y|q4Imx9?ra-SHyqR= zL+kOq+{p^6xzO^H$VC2a=d90mhQRXEM^<=blCQ*#_Xda1Iw2_Dl%Tm7cRf*_EB*-_)>uRVz;+Xx5z|LyX)Xf z4v<^;x1HqQc8&QWIH${$MfZAw>z@#W=n+XLnaZwD{s3(TiFy&bx}JwR?e&J5bg(Cr-oR}laz++dLbk3=10OClha zAhf^>MVLN#B!VMvGXuzlq(oi--pGrC)Mp^iPhkcb4IVEbC;kE;#Xq6=`*sN2x_~!4 zI6%>|6;zu*(iafkqH0#eg%fw~qEH zW*-#;OK+DlP#y*~xL#B{Fuc@dVqiGVEC#aIM@0eDq^SqFEJVdX`(n4Zi}k588|_ow z!3NggCQq3Fh-1PWtO3eP+81B*bO)PgUt|t803~2hz5?}wKpD)UOtSmbYr*ai6%CL< z4j{Qs9~ICDkPOI0h-Q>Fs5ztn>hEb^>4=El!?L zvt&wdLpySLj37^d3a^*Rj0_B|pu7*7%kfds=n8h|5CrS}@1vpsX_3CP14)9$k@#D# zf`%~vLYwxWRXxzK1|?ER{p1F1c7pP6(amlj6$5P_6_9&<7`;TggJqa6f*Xqloxw6C z7TwMQ-N6zdSA!cI1_349+9v{oUc3`zV0g_5W^M+x*wH&0-QJ*BQD{908U*V;QK}B< zK6GC|?LdG+koi>ig@dmom_ZR@VeKtZ!V7KFT&Q+nc%6XgQb>X#72-?K$qe1z0-)#x z^}|6!C*ZyeNSuI&t(|2`f}u9-0JZJGO?2=e9)DZP8_>SQSnyaKWS|aIPlc#xbO!Q( zV!5-DCBDjT>&bA!-GRZ-(}CflrvrnRmjlCD zF9(KrZwCey9|wllJ`M~^eH|Da{2Uk-`#CUN@N;0e?dQO-%in=PJHUZqV}JvLQJ@2Z zNst4BW~c)LN4Nt+RJa4f$_NJr#YhK+*O3kk0#ObO-=Z8CW<@(NEQxkt_!;fMVDIn1 z5aRE^(CY8NFw5V80Wz9?<_ze3HICMAB^KRopp%XayZuyJPnKwPJD9W{DB*&P$zNs2l@PC9AHm<<88V$dDhAyJJlzfg-6yn9`}MXM{Qvj= zKl8ax7ZsCkSDj8D6_;*bjZPO8k8W3!?pPb}Ftx8ir;CbCw`%~XsO$Fi>2y(1=>+9` z-w^O50BDLJqPy0@12ohF73s`Tk?0Ij5dn$WH2>x=R^Z>}#OTz0`tpP3!yMhGnSX%! zmmh!>G5-LGfyRqmR1}oYce8ZxZ#x4jvH7i6ac{MI)>eLAM@C0C-ww9Y zZ?B83j}`}OA7yq?(O^EIoueWHiazTX#mP|B;PG38dYC2MIVvHZ)<=tNv_Y;3>2y(X z=zf7v_F4vEd9ehnz(&{o=I_NaDDo1gIhPT4IGl3FmQF z2GBs=YtR%*z;Oo&P|NqYD@6U_gD;qSd5(g*ecBhaZ+4%C=2eg(9?Cy@oftbAd)*j8 z1M)5^2HJ<353qvf3tUu8w2!fZMpZf;1v+aj;^R7FZ9ub8HlWhx%$eplHQiHGK((py zfmTq{+y)duoh~XapmLn zc=Y=CcsN)eDtg=g!3dQEGtDe+(^wF1#z45dmS+J~W37DW3oluCi<0ESZW=3}hQ|M<%; zycTeK&D(s8=`~05G3M9I&Bs_QOH>4kSwLHs!EO8QL;Tx)Ts)XBX%&DCx{o)%VAutk!z;1&C=mm-MK07i zFdTPLF%YPCU;s5bUt8`7sB>V@1#MIZ)g3z~z<#!2YGZb~sDP4^2B_az%5A#=)W>Yj1{K|K zlT~^>8M=H_B)UsfRMNWHKm!(9r#pO91o^k0us&7GbIe6WfFZ5ZMMdbfZk;^;c905# zix0YZK#fe3v~CuG4j&Z(hyt+U1WGW6^o!DRhM29 z(D`2s%s+a0c7hI1)jp(suJu5tW$8E2kY}&gf2gNgkCw1qe9?Ne^jGVF67F7)|H@~X zPjtV0&1C!*bR4rv{3eG7^yhYlANL1+Y&#CDgcD1h=~>uvC0 zCB(C5txtfbhef*0jc=Uqrf9YzF$GZ7@S-{Z< zD&Cn-bn+}-~hUxTD}M1wLVbFU|8T>$eb{%v85 z%s;_26O?9#(kxJ#6-u)~X?7^h0i`*iG#8ZShSEGxniop*L1}&v&HS_Zn1Ba=YdI(d zgO&k+ee$vU_rDhdA`e9vmk2l$9bUQE@U+zA1 z@Pz>XwtFBI?G6mh2U+;H9q49h1sia*`&MU&iU6tsMPDKn9?? z0^x-2TgFE*oB(ye(QX~E6PgdQ7#}r0+Aj`p50*C+rd7gjU9b*x2z;xe0@`ogW za6q`g_-OYnB#n_Q5C=isfD#CZNI-E$_YLE#Ag7?10*QsI-8$fS0D0jmMr0ry0SX3$ zc2Iyo><6U|P&6I|8F0Ayoq|7q>nqTB{6Cf=kM3&+UojaoB3uP>1pl@g6`^jMy`cE* zzG3_qVJgH$e~oQmE^2jTDl9KKK!Y2q355;}|5a1KWDb}t0FxzPvZ4^urw+6QE#~g^QQ;^OX?zJ{ z>w<_@MzDDC8|@R_M_L)XA9izeJFotxU=fIG%5Y*lcNa<7s(amo% zg2Tc)Q(qY0?u`AD#q?iPr_g~RvY9sobdqf|Zv=??FRB4rsm&X5#DSrct8mzXp_C^hCJ(emR|aI<3()q~44w@R3>h3792j1lV*s6q z2htDP);J~Lzp2L&(8&gK0(xC11oXNt2>355Qs}^t!2;640MY{L2{j*4S@;5UXrMu7 zX-|WlQmK_&XK7Cto7LeG78|9~Paq){Bq4>ePo1$BdR>=epwfYiSJn#C0n5C%GT1++6O;DzIr|NlE(X9WBgeFL(~^-VWNH)p4BM?l~U z(A;NaJoa9}v|U-biMxvi=M$h`~<%nTU}42%pfKojDTk%x^B96tPC z)c_oS7SQ+uh5INTJRz`aIRk^!3I+zF6$}gtD;OB~RxmL9Sz z3=9k}7BMjVS_E--&TvUCS95<}7Dma9Pg4P_vwYAz?WKg9%i<&JqR&izN&U zUP~AlB9<^PhJ8yI7|tzWV0g5If#KT{1_rSu3=DEh85j(f zGBCI;WnhR}%D|AZl!2jUDFXxB5(b7Ziy0U^7Bet>SjE7gvY3G(ZZQLc$zleE7mFDf zt}SL@II)<4VaH+yh6Rfm7$z-dU}#y)z>v3?fq`o&#LXeA7#Mt3F)-MzVqh>^#lRr5 zih+S|6$1myDh7r>D;XF*tz=+$wUUA1)=CD3xK#`cd8-&0s#Y;D^sQoGn74|7VcjYQ zhCQno7|yL?U~pQ^!0-s9e>DSx)M^F>mDLOk2CEqutX4BHFs)`_IJc64;mAq`hPo9D z3>_;N7$&V?V3@OlfnnJS28InQ7#Mb~U|=}1f`Q@O3I>K7D;O9atzcmIu!4c%*9ry( zwv`MF0xKC9BvvvoD6M2*&{@gAV78Kh!EPl3gWF052EUaI3}Gu77!p=8Fl4P{U?^G1 zz)-W2fuU_B1H+`13=H#DGB7M#$-uB-B?H4Q&>jc|28JZif@B5;2C?M~3@pnT7``oI zV0gBSf#Jq728J`s7#Q{}V_;aZjDcawG6sgWWef~u%NQ6kmN76SEMs5@S;oL%w~T?o zXc+^8+A;Y zfnfzS%{UjOCYGeSWEQ0+m*f{!Drl${Yl33NH?bfrGqv15%`G!06)Fl*3li{6EGWp# zOUDq02xR7^Ua{_}fyEOv?M+E}|hXn%z=LH5qc0mRP zjtm9{4h;qd&J7F<92^V`932b{93Bh|oF5nj*jX4DI6@d0I7ApkIUX|zvuiUjaFj4G zaF{SKaGqcgWanjI;7DO$;80;;;9SALzyVU-NO3>-QP44gX{7&v$s7&v+u7&v?w7&w102(U9V zFmMDhFmMPlh;l4u5N20qVBjcXVBj!fVBkE$AjrGB9v{g2sO+0|SRBg8&B;BP9My z85lTB85lTELE}G_fq_Gnfq`=s0|N&ryjmF;I9wSRIA1~IKbC=kLzaPoa~3rIYZ(|g zY#A6hZ$aZfmw|ypmw|zE7Xt$aC_VKuFmU)XFmV2Y#y=>{2s4Os9EQe!F#`jKF#`kV zF=+gQ!c>`ofpZxH0|zL*G&3-8I5RMCK7+=8Gy?;NGy?cbOr_vbp{5` zbqov~p!C+xz`)_oz`*$q8vpSO3>@+d44m_z@n6rtz+un8z1Yr zH2z`ve>GbEe+`X)SpJ_4jel7FzYUFlSpMIQmj8c4;~!W4KMsw5SpHv*mj9na;~$p) zr$gf(mjACq;~$p)x1;6%@6h{LE>eJIhiR6DVfD3iFwJX3MKjZ3TcT& z3Q36|Z48-tX!6PVDXHML6hu5Hvm~`BF-IR}8%P#iPq9LAD(FsWn0QiV3CwjxsRgNt z$a1LWL3Dzgl$)4W3DcBcgm7+gWo}Y_PBGk_Am>5VK{O+|wYVg|C^bbPDJMTU8>R-- zyo0+7EKyQfkjlW2k(!v2TBML%l&p}OS)7|#lAOU%nwOoIU!JD`l}pP>Oh>W<7QRqf zc(_BtsXQ|;CBIyuII}7hZccK3Zb4CMadBpTo0;2(=10(1LbhzyxTR}F0Yy;T@ zvPFzRj6s}1oI!#?f!5<<$orH!n@s_Swd{w`OAz3`)@1nrR=R-OMk7{ zYML`+lhd`6yKg)E+_UC!&JCl|nES5hd9U4xw7s}`jloSdD~5Z44T0BBh$UXwzxB%& zMdc%#!)8w1b)J9e-W~IHFllgfu*Pq?$#YEQCHKbmntXjhpNxprvWW+6baI09C#pS6ivvIr+*Q%2>ymxI1Sp!x%GN}s) zaPMnV;5n(fH6wg;YrX>Km9(AnpCq2+`*|;J#>tzSN;58O*s|jKadF$tk&V1t^sHm{ zZeN?T>wrS@jsM>9oX0bcPLfpxZTdY&2*k$GN zu87L2;y%y!WbS{?$?EGExHisXuXUNs|KZYNj`oaCjB@`Dv1P`FCw)Jip4w1omLvI` zEjzDF`pUlt+Lt?{s%{CK@w%Iye}CuCpYQkeIq%ypezk8y!i&Y%uGLMxxbVoK8x}#I z?)!gC-+eJXe9yAGY+LQU%{F=5(&oM4Bh9t`n-{ZLLKTbS;r9vmL+_`rc;A<2Sh+9L zt!H!MX{qM4Jqs@7tC~H^2%1{XbB5WSd-qN;CMA6}*3f;{yN+sb@7**hYKt~Y*5;Vy z|F0i1Jbz(p_q>}rvg__eZ#a}PTkTWwn>mZKO1UQIGOc9eoMUgs_qsKmy+}BmfwjqN z|5TBx9go&&Z>+YE-o`V#@6sgReOI4ue}AV&@&0Y@E0->}IX$}a@yF)78F|gOW=o6VW)%ECFSS2&UDA)s|FaWa&*z9$JmOmL z?h^0ykY*OYLz|f`6V=j}e-}%*;On00dAmH%&MWKUx;s(VZl-bHclcm^!#sH1o|Q-E z?Y>ukev|vl|62`YKJDGxb!gWqgUOqNmo46+!fJM5*JQTqXEnp`h3rqisjOF(wt0tF z;t?k4jMypK`C8`txVJ3q<2fvSpEauYJ(Dh|40AM;mTXEgGddistIcvWC&l$kd2QSB zsIcSH9Q`@EZl3VmeduD>i}$}y-~95J$A9Y@-@0{M`*fS9w>CkOF;Q%@a2Lp z6_Xs_Cx%{Mrzi4ktNHb zd3;f47pb&NeJnb^N5O5=^2G9$`<6X?@$vbE&o5M(?;TFvboAD$b+NIp=exORoXO75 z{991AF5A%ZeUz4h4ktTzu7!-yXEV3^o7iKI6^0bNHAv0=^8K^c?rnz*7nM$w=`>lu zKH>MC`Qp2JTlgyPtyHymzscr*<>%sk9xt+LrH?w=Xx)oo^{@QkQkV3yUDyB6F`1Ct zj9sEr9Cq{dHos6_cIc+Xe%_-CEPTG~R_tFsjqBXBUSUp#Gcg{9*Im1le&wACepnt7 zyL*>t&Vt3)?0Y+=^G%x=Fys0?!Ifu@U;6d%&E(U+zQ$ZnDw2IxnSF0-h{OCA|Hud- zJ~2sUQT|&67AmtHm2H=2|4n@s^{hsf^K^KM#Rb2`>n1Wjn%^a%cxJhA!rwhw>mRDD zeRsiri*B>f{G3gkZL3v0PQDTizM$z=_dBCJ_0hLVd;RSmDrP0poU*1`LM)&51bc7p z^$04sms)A~zAoY4=V#CNANu>RX5xjbwhK<5`DNDCyqkUEtcs958!c0pukfDHY|XWD zhSJGjTRA&UuM_Z(eIL)4^Tq9~{mtASzC$I>LQ$WYlpS9kEXX<7;8=dc{kqw7wr9G1 zN4GL8ZE2C-Bh}?t7`;0xW5$be=bJZkqU8K_MdRztc;@IztKXGjuoGgKu`}4PdBKFF zb=?<(w@wl}`sHr;m-k1eEI;)A>a?5RUhZLEx1v{Od-grUIa=?vTHjV?pIYNla5-N( z_J^L9+rx)u_tF-yAFWRgc@d(N`q}sC=S@sY4y}|=n zZ;DO!yZ-Am|Fege&TKvNv$y5?123Uz>$#PCx1A_hFt^ijcZ*xBGBbOQh13yyQNva~ zJ`JZC{^`ssL+TFvstj#Bo#Z2b)S@x&i}H-Q%lS6nnI^hQnjz$Al%fBhnMq04ZvMJs5l7zc@b%SRMfUAM82(X<_e$*Gc#O9#DS&?B?Ew zdqnyNdp*Oa|qD^Q=RRc~@GPBlo#6-C4oO{`$~qwoNs?EUjYxtUCgQ`TDId z@?UG6&in9vC{M~yDbC=@H#oR-=W*G($8Z-cR+Di*`9xN(e387SY=YbxZhh(5iEpJ2 z-Cr&7b!(>NiBG0N|7Uy`UZ%EDaH3DKfJl^$sItL7k*dBOV!ltR#2>i%T3pxZx9ppA z(t76)ZY!I2F816lZFWIc2W(OUm~11&LTJraHX}cx-(JbV$ z)A%A&sC&3~dmXtJ9PL^}3IJJPK>hGyz)f@Eock^geSue_YL zt=Z9U%Cne)3^PnEK4)^&uTQcU%}q{Qt(_Qn@MXf4>&sFf?@dYB#jKRpoB1GJ;q-M6 z@r9zEe)7TIm8BEC{v_Y?Kg}uQH)(s6&(gayd}qu$;eH?}W{Fq!x|mif14Oq6SWT(17w_y%dyhuaIkKX`Y3AIL79xoX_*Eb*fq-NXlK``9y;}l2EFN?KLfBiMJ z_xsuZ{@)T}D{h9H@4CU)`RA79W4qgXz82o=p1=A2CB?6IpL(0$xxtft{e9ZXYuj$T zztXr#_bSVhqzlI97F^6Kcy>8fO68Kkzqq67Q|270)4Fxc(?R0+nXvFfzip--Uet8? z;M5m_2Ue{2Jw5+u|EXiuCr^A77QOHO+DK1V9Jlom-WUb#$c6YTnNm(e!MCT*Jj% zcN#;^Om0bD7T9VfA>PhgbhT~4g-0u=?^0NCjwNl?&%7n8Ybsu^^A^%vFSI6qt?IG0 zYa|*!F4uo;ysRv=bcv(=_N7NOe=PXyY_)LJ-zw=aOt&_APhV)_JyR zJJ0R2o2)nTZ%*R(+!&hLy&*&C`tEoS(Oqocf_E6snYeRH*S$T>4`lY%kMu36G)DjA1)d2%V6mXk@*6@3V*C|I&q--{@Ufo)15hrO4ahuoh_(e zJ5^LdQ1|!tCHC_!NVKcJI4|YYU%Y6I!m$k}-K#k4)*CH|KG7$!HQk!)0z>Xrvqccc9K&)aJlm$wN#dG)wFB8d0KTZfGOfj<`h(Ee~_ z{&Wv51Enbc@Oh8E<$Kmo=JXPn%chx93 zGEM6LBJll%#|qmBk=YUUE0niQkTtygePL(T`7#my6{prbWUt@Xl5m*qQo(|_?cy@e zB6qC(Vsl^Ar}g=viw_#=GWqmRS7&>aA6~@Id-{0B#$wImEQvk~cHI0rBj07h|I2^s z0@B_|o8JoaRo}^{;lR4>*!vAj7I*N-NNu>?Sbsg;a$?0L?Jx3qlbOG(S*J~y)!bpP zKk;qEWX+HSmo0|uKX~r1@Ri$j{^PPY_b2`9+@a@|EE8Z`X8 z9s1lgdCmg+?4QS*FNur&5<1Ri8-B1_cepT|#{~Sf@Bu=;XThCt_ooMRo zE4VW6t;WWmffM4xo-|n*Mp!j-_Ah^yXDrWgu#Go&nbp=M4;}U%Z2QBiujwi7<@a^{ z=Q1UuX@c{*D)wp5ki9uw!TaQw3cYn62M#d)y|gsVF-sw zGTS$y?)ulhi%HL4Z@U*C)W>iFn{EsldldIvcx8CMqdzR!Ao1rlC6;+vv-U3A z%YNe^<9@TeEo*~0IYejq70LeBJhxP^t$KUO!{hvCZwgFWv2Tr*o1mnHa@o>`(zIhY z1Q@Fl{;fM-H2Y-nDW#7~!riAyykBX#_V1ZEL07hSzGcasAJ6rwx!if-_#-8-Qvauk z++@dF8`XmMUz_#%<*}cedltHKC-HrdWY6?jxG!+F&22OF7SoxNrUm@d)0??7MD5SL z7xRP)V|&WQ9-5rfGh4aZvrEwRnLV>YWApB^i4PtNe~XF!8~M_6hF$1KMYg6KmqfcO ztIl;FJHOJr&cd<&TCCdPyv?0uHP_yqI+A})Hj-=O!iP`fL_RlGuJFE|d0KcAU-{~o zg@<-p9;;()__uP-)0v_w+-3{R@&aZ0mW!pe9V)zKr*kh~$8+c9DIXeHIHx|_v9a2d zcd?w_MrjYbOU}BHdA|>TyZv(6#PmSIwCgt3?0vpNmwm#1 z*@?2-1yZs$D17?7v(TeupW{8LTUTmx?>>3`<;vE;=F6?eg>zf}PH-GWlCiimiezne1IN_5i5O+1f@yVj}!Xg{yCl|e0uDD;~ZT-8HYiqXY z))l{;HqFSnz5C>~HG6(K7~RNu?0P?@^v*Tj^Q$k~MylO3SQB`U!Ro~Iz=r)762%m^ zeAybd`H1rQT~lZ7*t?WpgK5XSc-9V%V>~xEZRCEbqMu*W9+BZAcra0R^|mx!+p;^3 z6But7vdy@pu;=|%0jn4KN?MJ6XPsWsK$#-}6(zZ|xyZ>1TRRyT2cIrx?TNO;WGs`|*#pM%!m$&9*0BfO*I+Nq7 zecS>9CwUaw!ZWt2D&)6r-kElV^IYPS`EmDt@@d{YIb*|x8A`{muhlWVJ zEW4_#s1;B6&T~)Z{6CkAVV!y{`@D@G_$Rxxb1c3j$M`8DlkL#I?@8gY4XNp;C3DOQ z^Rn5V|GOex)_Ga`fxxY*sPwyDXMXOypWnCd{ZH}j``j7Mf?c*>{l=5}XRViMaK%BrS+beHwMO?$aDw6{b}irJjS za_IX1)P{-jo#wRjFw?qsexoNOy!^O@Nfv8T7PGK33H z-S5@(Xh)Su^+xS=Jlmu#CSB^A{q*WS-kLk_w{zdVuh@3^(v^=_9yw*)-TXuTR&!qC zKC!dkcB)0^Zzz8#vE9AAgCpwMfBvk3G&bu~f{ffT{i*Z*|43SwnV9|mvRKY}*9BaU zDz5WhdgsT|9Ae45`OxxowZsbvV&6S8-F@xy%5Sf`nB{fzTGSnf``l^fH>^Lb+_NtD z-tKuv-8Y@DH`x0B<=(xYWKQim)D^sWvVqE$#mjbGFk?M?oo#Z+y>LzCo9X*Er&Z}4 zN%Yzgn<34lm9ITz3->oUFX1?7K9X+uXNv!o_%-QdG1IY(J)%fGmW zMJ;dhcbs+zSHr`K%t=h?ch&Uaq3ZeRNhne$$UgaU(aDhpJ6 zDY%^Y-f@!Ny6d6lTc3%@%-DLJwYg&WUe^c z-#;70H*ac-%A%h6qK}tva#Ps1vOMwQi-*fze7^Af@V#c0TSqsg#>TE&<>EH~b$<34 zjk1EjnU;px>lC!2-gC2a>IlhL+QEn@#U6ia%HW&wAmp&+({qZNxn-n-7)#tS^)5Tn_o`w%-ns zIW|SKi?Nq)x5F~!7tQ-EZXU8&aFkbZ_ZJ_oY0LYCd#9a?Im5u|dfm_??^jZH`NQB- zyLQKhEMAZ!+RG!VB2snObrQn-~zb<|Kb$W79()F0^%4f0;AzSZ7`nSv% z;}eSD7gd&2u_(A@tL!*C^>6m_nrBhZ!cTLm`dzR{VVt;bu|(JWN5;#~C~EEbo3K{p z;rcE17v9YmYSwMz+>~?DLuK`aV9{5<>)bRSrIu&t+gE-wQ}Ng?%PC#LBBW*N9lYmL zkVo(4%G7%W33cxcpFjKj@891;`>$S@Saas|0^8;;vtP3&vhUuwC!}J<@>EOf<{92f zGgfkOZvAyqVBP7C`1i5?ZeMcva&Ow7Ejh&3^Epb$`IVzG)4`mAgEz_@8>X9Gckk1E z#(M>ZEiHwPT~ZlQyQ7`UU(ATgxp`Am*I$mutS(+%T6c~egUsEXGZ=&xG#dtY zuS=RRX>0JsyI+oq9eMvH{QaTjQ@-7tc6A;5o|oHYdRNRbyqDdo^6-Hws69ElZpYty^M79Z ze8%ehi@((kE3@~^+!Xb9(|k_5)h!nFIx;F7OxZ<0%4xY7u^W~bdlWp}5}1AAYn5BG zS#s>A^3%Vr`(1kYjQ{7Et!ExwZ|Pk>O~`9ouQK=C1qCNsb~|=5E62J?S>&)AirOF1 z;Nxpe_n+ZZ7qXH$wDQ*hpQO``jTT4cXDEM(+swCo?kdq~cbcXn8>rPENBXdQ!w@cV$+U_9v-7hAxEx36huI}?2_tZmcb37*Il?E@+ zw-PdYq0Gs?l0zjVOVB=5d)o5KC;fXomM)nnowm12Oa0U7Jr7=A=v{c=@4cj(&)zGG z?0LACclm`sYF*9t_7gYN6*&AbRd-Uew;rvF`tmNA)BL-KMcEF{ zbsHB7&HvD4f5v#S%3ttY4CA3JbLN&Sc}#BmBH1}t++jO?=rv1k%_dfVu~t6ez#aS- zt^0YWw_f83egBYC>Sqeajmg1W^K`kmW87_J)D{=WJ~`kyy!S7{0&7VCvP)Mwhc+ z84EJrRmpxjL3L&Sa`pFy|I~D&Pbw$*`Y0_>uU2?AQ%Oxo(V=8M)A%<4T*81ejjv0UW3qC3Vj%O|VKlm+^0mxzBXD!n?} zwXX9{MZNbn|5{!Sv6}Nqij@r|ODde?CRed6I8}Y@)Zd&vdK+@v8?G zf~V+0aqnPxKd*_UmHzjV|M@{C!S9<(lgdHIvqmls z{1L@Yp3k(LyL)6qK4t`j8Z)Jbm+r9(+kTBR_{V{sAgfio18PJb1@5c68uiHXMYKZj z{m8VpZ4pb_H^#o6XcD9On>jAuH7b6se(pn4>4pd23%5Voc>dkv;-xpA*=&w^{_ke? z(;ewXPpWuX->Z39ynCX+|7Ov=u(t_c5?|;)dh+sZ$K}_n&AMJ?#_atoyyL^4i@p9(^{12Xw%udPdxc;3+}}LE`|el8OLxq@pI*=Axp8e}+WRZ- zZ*04&yQ%R)(h`=73(gr|epZlmNkuC5Xxu-6BXg#xAG@VhcU;23^H6x$nZr|UejmKt zwCI4~i>ar5*RMF$e{}wdlhwygatnSu!WK5k=i3~X@7vyV zwsX_j+dP~3H(GD>Ck2QIGf?!Qa?+PMl-7=U&&Ay)qA)_eaIu+Bd`G zEvWwAu_rxs73Ya=p{?uR^(RdaH^2A6NHshD&fM!3JCAPBcwfum-<7}jLCST;c1Evj z`*(cGx^efx{c_`oWPz{%$?*9vmKyx}B`{yaY0Zzq`_%_dq#s|twzP=D`RuuTwW(|C z3v>k~MD3Ss|J^QeVV>0a7wU_O`<*r%Q&_`M<$iL3(Rw?Hz7x@0*6CX}wKH7M53tCb z{wAKMHS)tF+o#jpQ`_1v-nDwL<$S+?RJpN+{i`PeZ9x&`j~(9L;QbM}KjVY;kA)u7 z=O2kuGSGT7FWkT0GymK1uN;%FIxMfv{c}LVbp8duUghoD^QTE1RoALtu^Fe4i~6VXM5tKEhCT!nV$Z-!F@lozGfxY6btkdiIBGhZ9=j78G21CL_N6 z%gP;*KBD(+E*^T`npxNIp!&2v-{EqP?9;sbi;6d998Y99u6c9E0w0(B8DIZg{y*Ve zT0mXcEputUo$9{ZSRFK$YHZrxy&at)v&GZjs24C5e(>0R(ntT^?Vk+> z$*#{sv+Wnmx!ipG=Rva!@yag#q9?KDr(+%ltn&-`@9^!#4Aphr9mQhi@5NY}9DeMv zQ=hB&QeI_dXoX|X<68@bIo^y$So_kg+$|KCVnO|n+Zxz+B*dBW)B^Mby< zCXKiAu1pC0xv}X^2RXJ{Ez8~O@Nfz1pSFYIo|^jWzxsI@ zDV2ThnkP7IhW5S+h3PkCE54lcKH#xV@6un!16ht~OTSNxlkwNGtc;yMZTeH?kDq+A zC0V~-uSAl1>_rGf`SGo$uohkEu$9C>xXYw5vwceB;jxT=dR|Yyx zmNN-f+j#5stZVx>|2+1R+jU`&o(Ho~xPdpSkXCZd7>uU}9Nx%r{}rmyv%zhT6@@X<}2nVwdQ0 ztoz)mI`ft1uhlzR-L1kyI{Ei>rd;0X$;s0AVdIWxQy24k zR!eWxlXJdg=kYr)QupQUZ-)cZCoVg)VWoTt&$+6!orzQKv9f(+6agLO!N70;R1h#Q zxV=N;b2CCDVDg}iwX%5*42F3Q3^Sl>r9mq0En;AZC}Lp90PR5p34qpbFJ@rSC}3ci zfh5+mlz}0lfPp~=Nv{TI{eB?>LkW_)4a*rAObQqn9FW99RxmIy6fiKTAn7ey!NBmO zkbyx1yi*2ZvDPXEh8G18Haa=Fe_(X~06q2(1T#42hvsGGWtLV)rRaAaWMWpqz10c}}uEGa3BzwFgweM&rzF#gZy0Kscs)5G;E#nD;ycvL2BLdlS_*k7(|$YQ}a@wvW^T4 zyBS>bQUVe)^GX;P+(7DrD~n4~bA3}wiZYXn85npNoKn*>^PnnP7+g}*!okZFLEds? zV7SK^kY9`xlRBV~aLGycEJ@7`Nv$YxWLU#gUR+R=nOBnL$Y2VxAs`VHoDBS6mq3-m z9mLEOl$w*8Se)wO%-P<|oFW)bz~alGGw_B64J42n}`(f|Uos&~)pc zlm&7;!)M0e)DloYIp^nqR%5aV{mGTQ?_q@N-D!~#^BVP)Z`MFJ~ODj zZuy{eZUE)Ecsga4dZ$)8GNd5*U~P^JjxJ%20WfDW6fp#6q~_$fR-`7EmZUl|1bevpz!Ld( zrs9&K;{71myyV0ZhHW4gh{3QA#L7v{V_?_?Vu5w+ z0JCxn7#I$NSYQJqn9?8#mf;~|d|7cGl*2HQDK{}ECqJ2iVI5;UT=*bJUmnP?IHojE z@y4)~skk^fF)xjQVG3h>L1{^9UKzs*rXp}IXJCj0sY*;?NMTAVNzKV&NMcGWPEE~b z2xm&mFG$T}@L@_z1{LWH4E{`MX*uASiwEfe%L+5bXXYm{Fr+c1l^11}q%tt<1=*ig z0xIwsdYN)Sr7W0pfeB18Y+{0DKZbV<@oB{+przC&Kzd5@a~K%<7~>0)!ONLN8RIjH zAxwruhWPm0r1<30qWIjz3I>KHOoheyMI{W&nTk>&K@h~0mY!Nt%y0l?QeJXy0YfrF ze0)KCe0*Y2dKm*l5`ve^z~CDk<{T6Pu1py&GsLG>WR@^^FvLeB7l7)$lGGvwPbRP+ zLq9`&VsdUuP98%9LwtOCYDsQlW?o`ZdNBh(V?3w|FG(%RWoTxIk1tLwDJ@O~)gu|H zMMVq@a~a|jQ?g2nOXAZ~GRr{ceS_VToS&Nu%0LYH5ErFEc#RMf;u8xB;=$f$I0bT7 za%w?IdPR=dJWMJ42W~Y=EFkEDeFG~%Id zFn~^Y7TM&$02*as+2p_gI%)aGMhAu$8yy(#ia9VqhgTRbY;<6FCyL-7*yzB(D(S!g zx+VtX-VGZa80wJZ=WKLf*oMTPu+f2Gy97diaDG}zd16s&jBjRgQGRiLT1gC~xmX+n z?OQ-QHZh=HYH>+oP7VV(dU7HCsraP)3WgNeZu$6Ra6cEcw;j|iLgF%{r!~g zXJn^VGBAMo#RaL!nTa{^Y56%RsYUSxi6t2f5EJ5yGxCc{l1ob%oP#}GeB#~w{X&AF z?0_JD_aH~#U6O!aSRFx4PZc1hY*IUb8!v!4)G6g@eFcx4)G6) zL{pC`4b|ru5a8k%;s`e%Y_PAZUno=_>Mn#ZgHL8sOmSvOs%}AIa&}^RYH6>E-eLv3e^gR;M9`v{G#m4yma_zgN7!u z0tN<<8qehXJW%Hxv~3HUykk*%X>Mv>Nin))aB7K5YB4yFGV}8^G!@doj?+Q5A~>}K z+Ukqv$rlL3%eY^&nUB$)4HO0lnnV>w1JeGq^ zEl3easiA>^f^T9qk(GjKG3e45y@E;xBLf2kP-KQ}RtAtWQSSRp*KDzPXHNHs%)HW6h5RA~XV3=B9FQoey{u4@k*W}! zT2fj7HCqqlcbEJ;^%4cpK179p%94!yJoRFjb%~%zR)92k8G=iS67$ki!RJ>LfEB8y zsHQ+#=@5+y(1J-pHAN46JO+bvVjd{fLS4_m;Fk}z1J#DK{L;J>h0Hv#1B<}@;i5{A zZqN7lrk(r#KP@Y%}&KNk2V1SyTTC9*+tN_!lkd|MhPyz~N zXinBsfb^JA9afUBP+FX7g(zPkhQ&ZA=lr777}eq!@Tg!;4k+a^Fu?4Cheff12HbHl zzkv=UftT43!;s8mU~taQEy&Et%u81QmBPiK2-GX6%-7S?V_*P{pMhi`aR3&=p*|Tb zi6{d~^QtlnP(27Xo`C`C1CX&`5s=29(!4y7KtWNeE;Irm=>d|58G=$1Qz}6&gGz$Z z5^73J0*wWM;}og{qCY=PLA6*-+!Gu-@em3!%F4jN z0B=!24Ny`oj)9D~sut^|R-`H^=$0yg1XYW5%QN#p$yzTbKV1pr?(!rC1~lai1||jy zL8<8qE=~$yr&}pRs}{$o7RRa<+k$Ej=ls%~6ma=foLZugom#0-1{y;Hk4okwR)SL) zq>1>c0JWvy zegX|vfIP*(5Sd@9kdhDeXlgE~)W}FIOI0XF>?BW2PEIW@Rse7LF6PPy)diWw3b~2N z8JT&YGX!8o0Ho9gHH$%wWCjKX4{z7Vc<0cdAXmSTc+lV}Ow7mM+0iH7*U{O-)6bQG z0W|Uqim=3-99Sw&%goCx&PYws7E?$nEhzyxB3}Wm=7g6fps+&3A99}2 z($ZpJU;xP%mFC4m?Sy0;D}_u@>m@HS2hr?_fCOh+Q7SmVic3=ROG_9Skoe#cD`Nu# zg`mGABi$peP@lj5Lr(f}y8yfow_9WGE`lO9Yjd zPKha?%DpHalJa0fchDTjz>uF`08++~m!AhZo`NB9I!Ab(_6ts85qhLKxLj@5@?hdVSfsEt__m&k<$uP z98$zFl!A-T#GD*xk^#9v7dq?>FNYN%*)cUmkwG;D+89dAOHoh-9UTEWlqA0>GZoaV z1I>x05Tk|*ZVvd+4}=-X@Nz3DRRQ7-P&b$=Wf$D9Xr-5G3QCp+MGE!14?^@fq?-LzTh$=CqEt3)MQ`)Eo3#)Q%EdI z2Q|<^-38rJka-|7yeK~}T_LYDHz~CU)VKi;?ZeV9D6@mwVG6L*RzMM&n46fM3XV5$ zAF2S-RD+mY1e%bkQ~(zlpi^5ap&6u@0aC{!r&X{U6N{2FGRsnt#i4a8Xp}x5G=&XL zlMHn!r3{|Xb8V8-(m`2XAvZBSGa2S~jpEc)g(T3CIPtJ{LqR2|1qJdc1jELYAiPm> zGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E1VO9(K5=hYP$7#dg^7#J)V7(n-OB6*IH z0W{u|fg#Sqkj(%VY{+3?V3@(c(2&c(z_5XVp#h}+1Xv2m80aiL=vpgeQPAm)oGc6s zA5aCsr#gbfq1upS85uz00vMe%h`0hH1B1as$PvRZO%|^i7#1L@zww5Ffd#af0IJ}@ zI|ha*B)-E(1_lu%^%|f#Vg?2V(0%EQ(A5MBK)0Af^~0$D|Nk>QfBu}|-o1Ma*REY- z*t>TxLrY5wgOigJgQcY_q0v|QyK68RrP9IWmgEL{Ho|NrmZyLTXM|NkM8nBrh{Fnu5!{=I`(1lEN_ zpo@ddhw1yjZ9mAI|J#-=+yDRnw*A{cB#4bFz6>OUtnYVFG{}ZuL5@yQ|9=HV27yRN zr)ZG)zjyCJ;?e(q1vxr8MT5+L2ea>+f*MHQQUL}A(e+COLi%~G&BT}j*d}C;!$8(0WVbh1VooD+y6~LblFk? zQ2`NYi4x_t}`45A>@zX^y!At=X*3W%=%zik=liW&w{0kQu}1u*S%Gz6Irvd<$Z0%D(l zyMtiVub>E!-2(0oUcUk{{3nu{BA_Y&@t@eTWguUsrU-}%Fo;eSP!#~liVBEM6=1+- zUlcezA+~xz*@A}P5Qc=aqanI|3LyWj2gQ@ zl18FHY2u8;G$V#@+@_v7m$r5<#=? z{r~@e{{R06$`+vX_722@VbG=0VD|t2|3T>oIbFYd2h9;s8pMNPa2^3?ko_QkqokL2 z??Ao+nFOIhBossa8T}v1N6qi=Ksf@W4?=@TD29eBIE+DA6Ql^9AKtwKxg1oOfN3xf z#QNm`i$^c8-#{urp@|R%|1a#SPA11!KhwgQ@^HO`@bvOns0X z4@vV;L5_}&PEkSNJb;oPKwbl5csc`R=4G(FzHJ$(=0VN(|3KvcSPzJR>w{(W|Hzdf zIvb<|g5mlgT%yS#qmPl{|9?=r2Vn<>10IldrPYj^KmrWG45hJ1!x zh609Ch7tyl&3X(949*NW44Dkc4A~3{41Nr$3>6F|30B(@Pmr859`u z!R9!FeUQwM3bq@f7Nj#1sy73y7U6#d24r`Gr>^0iW#QNmP|G0C@SUNV;V;;~jc_?o zM}&`wA%Km6gCT=qF#{tDBs~8A2lXfr$`}}!0%{o+GkgbyGgJ*o7CnAI*EBIOP%D0T z;PC_UX%0gwLo(R^1q_u8B@7u1kkChsnM81CLS!J}R?1M!kjh{MaxNS7CPNX_MW8sVM6-c`fthh(KrMp=!*_-( zhJRo?n!zT62_^;xV!|Eb&beTBegK<+6n7AV0%{rNF*qT)(-S47B8LZIH#0CWIyf=1 zGZ-=OF))GSxq-nAoH{|dAs-y-pmeRkpa+IvCxCm7w9BX8806vU0AF58Vn}632fG!N zyC69PSK0vOOHYP0c-{r&bWoXr$eEzHEn~=JNM$I8=gMS;3~;_o2j@PJ`bw}I!W>W; zfXhw=a5^jjr_VfwbcSLE9dHSNC>@*_62avJ$aTm*C9X8UT>^k&sUe`2VJ^cr20n(@ z%nS^<48`EQl*3TW5YJG+P{feWkPc4I#SFy^#tcTFb!KRyh9F~*FeA83=w$fDz`^ha zoHm;naHljVGhYSVSGqQ6r6fiI_On`{K&0%0*5MW?nXaMmYAZ!6h_<}@0;S8b~1Q;Q53=9lR5CJG( z07}Ctupa)UL(4fV@z;J+pfk6REBa|^XFfbg@D=IEZ zW`O$j1617uBt;AiAax*HPz6VMqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Uk2D zAgsGYg{So!e@in114C$cj*3Wlca4fbaDyE~NkLXj*niQKMhAxO53S!KBTM4LkFhcQ z7oE}Iz|ei8`S<@)^(=<)|DsbG92i;;lnS@LE#VK(XlQa^$YT9}wfP9oYxeLg#{X9% zBflLMD3!_B4jN++-2+nnwuBc&|BJ3@aA2@36)EKm|1Y|v!GYm*OxXWY zk+3Y5u<-w)3mP04!nB{l{dbcZw^|4ZMkUEg}GiE?yB~+pL2#@iB__*kkkWjX0t`*=2?vCXM3+Hd0%D}*o zWzp+;B8ws5ML`q;L$@nWr|XGsR}qI!*E8L&5?K}*63q?_ovt@Piar{D3+Rl!;`ZXZ z5d%YatxWR~nbrd(Jgxsrv;s01njIKkOM{fL1iUzA!oXmCEHbi0xb=StZ$QQykZV}} zUk5qnTd(VhfEQQ17#La)M3(446kI`8@LJsXn`Nm0C>|yLm+}O^I3LNt(0qi)E%?9a zg9ZnN`0is+|1`bV zy)0HGEXD_33pA(xVMuGPUSzyJROUdTlc;jh>U~?1^gGi0ZQqqA`HDPtRN#hU0-y&ax_~olvsDV ze(84o^55Xa<_HFcfd8c*z`XyWCXEgZ%|`@U4|Kb7@VDForCV2#ZeNjJmJ`hu44tlD zN>A|bcb5X`fZI|4*24qgcX70wEa7hWsb9)`j3LIM^gTp4;Dr~+Y2Cg*z^?ZDUn*gI zAg$YtCjduv88KwWF68!u9IsTVP{4Wy;{9k$};6>bD zkgP=O0sfZrpzK;J!B8U6?fShUIa8U9V;ol!G)$RL%`Gocjs7$A? z$cqM$Bq#<1z<$w$O1;PkXJCkg_)s4$Ufe-ak=?E$T^tQRV@g;-;lt$M9UK7F`(G3k zh~2RQFE{`D|KIq)%gKNL{}1hc)9cC;kim1|Dn+I~s>vf^sZyMj|FflNM8Gj4w zEPc~i`k}M*NoVPo&eA`fr9b|Q7Bo39SRbq94#=2u#DU=@Ga~~-uj`Y5Ue^x+pt8Bw z^-Vyp>x+P1*E<2dt`7n-CL96zi03~vo>V}o(+#ZYPe4Y-dIyFq#;}Zv^&r2Lz>UrU z8NGr5l-&OGhW-f3=veQ-(9IF>Ul3xo3rM~LBLBbi0Z1k=!1`D%M?i+k5eJ6X1>LTH z{+HeXi_1X789?F@mZfjNS>k``8?dAfMDodD2Zq`u>YcG zKy{ewkKU;uxh%#YuqhJO$7*drIWwaK!ytif*B{o$O4z^(Il5i{AUOX;E1Db_j1Rn4 z>301Q(9Q8b5F`&_K;)2eKx7=a?g|V9WdTs?>~;MSkZ}MtYD8KOlro(JWugD2fBu8w znxhGvt}Q@R#vYJrXu3Ih_`j(DD5;7TymDZO1ZA$qH=qfTURQyD-d2V`|Nnz(T98P< zf6*RLbI6sW`G|lAq{ZOj!QTR^I6X>AJxZcIO5{BFTc0y9Ff4R=0csZUw@d~Vv86m2 zKR}UI&%fPOAgtSzrOAQe-~)m1?pl$6EQSn576yj@rUow^82(@TU&<4n!Gt6bANO(^ z1E`_^IVIq~D5!N4@M0qas3Le>pJf1XW5$OgU=Jes^K}x`5hX$(AKZZ`=ztXFS$g0S zrv;WX%`$F)%pbYSRYTj+Defg!TfjU}?v6vPC#>>y!ld?3uym17?$P)ke$dVM(p0>l27 zas*^B2r@AI7d_J8zz_!U7fAHK=nROX|Ce(77d-)LA+dFUV+NuIS`q#)<mFl79Nat^$3V8{SPe)#`u@x}+@UaNPziZoVmSb*}H zWEN8vLq-882Y{j*Z0Uc|2yh7j@>R2Kz$*s^h7zY{TabepN-e|wm#A?37j*#H|6w6W zHjAMyI*nd$EP%thAX#geC|DrBU4h-M~+5AQztk?AeGr^?fNB)LHmNX>z|z~NAD9mY&D1`0@kcj!PuAI!%g zJa8hh0mlNQoa%M`6BdxglEsjranOMwgNcEm;lC&gC{285{Z`5eDxX9}K;4`Jt>5^k zfKuc_P;nHtkCB0)!TMN#}e$F{@cfCIzfQl91-6%Hmar9DQ4wG$bp{)t2{S+eZh#3k175T4U^(W%kR_1u1(X}YAhFRc3rfCO%+LfK z_WyDKBq@Xa&;fNRsQ2okB2wy?#fYJ>TNdP^{h$_Lc*c8X28OWzm%}qYz!(lM9T;9* z^I>2J&v1C|!0_U<4=CgWUbZkYFkrRze~F4j#!RTi-5sFT;BJs10l}TTpw#lB(+5*`(Gg@|fE7sA&pCZ(xAo7z z|Nk>yfYbCfh=|Ys|Nk@gKt!64NPybIV2Ad$f@FdNkla}sm%+mbs7{~#fd(k#Y^=#z(cum~_Pytv{CE}M7+kczT<3=9mA z(yL4tRIHWwdhoaIVq##(V)(yBMMZ#t0o?4$c+JcJ63`F;c?(qJ!DQb+Box3Bpj`PP zhl7D3i!tJ58539w$P8#}4%3v^7r_N#_Z}4q0nlIv_F6UrT&;qV?33g0VyYWnsk*3$ zAga_0#~m0lK#mIkf9>T*MzD`TPUv>vur>Vu|9^?DEvQ1}Z@r3K7(ji4l1g802I*b{ zc3HPADES(H>(m9cN5jIvi8}N+Ysf1H2GH0WxXl5ojl;q+vRN1yLcs!{)CVp{f?qg! zFfcUiuz%^m5EEI#3(k7MFVsCixi|QQDoAnxNRlD+h49b+|ASv-vV*cK52!Px14;!* zrNaxSpa1`Nx~NDjRC(#Z5NUl35)zU7L9L1ssRj#%648te#~m2LL;qjxV6u?^V!7qZCK(Pxc8AU)NY5m}qI;2!P02;4> zxDC{P+3^C@kg?3r!DAj1s0RKJ+4`+SFpDwlg(KLsu>YbUqd-G>vC)l3KxzJv0K-C$ zE1*6{2}b~={EYlxqQU}cjW?@;10-@WNC~Kq10G@RHU;(bK`p?H8z(?bylWXpKy>$w z|JO2hfLNf>#A_K#Aj}{CuVqYuF#r6&meB%Xe))gR_?soTg4mPlCIT4lItZzwvKBc<_Nhw=F2I z288VfjY#mzgYGW zl*S|=H7UX&|INS|80t`P_0wI(()z8mrQ1bCAU+Nhxgg(zVyy8EsFf7f4KBLFGeDJg zZ|e+D&phCy14F=Z*FT_&<^MH^V8DOTC7>eN7BsTcd_(}$F1_A*fWNf`)E@=sv0m3T z{~?vI0?2^>*P4$A^t%4(HkASOReD=jfQDUqUDpJFLZ;#WwblbAa^Ma(k_@PA3YG!I zybeSUILimTFmYjE_+QEa5es;s>H-cfDEI$$&>&AJXoRMRfq@}7%=mVAaPWUo2aub2 zPk>0%BcL|JL5KZUPC76Ih6i-I{`r4xAxNtEfI>hxs5ZEk0m_gs?t{kqJ~SUuvHo0Y z0PP_6ww8cuu_a)iftwZ(k$@N1zyps0%_si9WCE2pr5s@|{(*8;r|XwUXvP9t(`|bI z++%@&9tS>yKU+3r1*TtNTXt5slW{ zC01Z#gI_#yf`l%M_5aczSs+zwKppT>4(7uRDi#d?uLZw&`}_ZY(5wK+%|}#Tb7v%g z9CZEn|NoJoz!O~o3T4+Xk>Few+WjUlFgT2i65+;42ZnHP9K45*L#M;u8qr}Y8 z^+$=JrR$#(9qSJz8sIDs&h6nZKx_U$O^DDx-9JEcGXG1M{$Kka`lI_eBGZHCfFw}d z({l<^LY07Ma0wLyVu8ZyT80mV$?^YMh7E)XDxowW%rF11Wk^7nAO2sWi?0ZOK_R~kXS`VjE{GFbF-K*o~OpaBT5Mo@z@;J@evuu9O# z6vV)Q|E3^g{$B>CzKj_l_d-RQk9dHFi(8O{AYF^z)+c}e|A&-|VHp(=_cb3;0JUa8 zBaOxf0y5Ybm>2>A0w78QzztAP;Itknk?DX4Anl-P1f&z{o-e0BO(aO*g+WyPFXhO1atb-R9)Nk^@+=^W@xQ6e zOVHflpZ}(yrqzGZGvIQ^0bJC8%1wkhpahVy1*{wH7ap)*6dd*~0f|Gb3;=m33_N^G zs%OHmc%=D=1gMVy%?3L_;~Xzm+d@Xh!uVTOf@b%iLt`!85DsXJiobOWC{Y{#w*)ub z_**oY7#Q|3TytRPJ_c&EN`dM?TM(atzlE2Hfgu>uXU`}&>%agWR`_4a(OLW9xa*gc zvknZMwLkt}?rnVoiV;v99`Hik7BspG7Kk_tn&kUs0}}`fc=6T-I+)yiM8f)biF9u- zNM#_n=F4yZsfP{53WSFfs6cr@H590!D-sZpVQ>~L`-1B@P(2n7DxI%^isl2QAe9Q} zDl?es!QCcN1<-KKe^HetaLmJe^&G_e@WSK!|Nq_Kma7dY&q8Fgm;%CHR6?3g%|HH^ zN(4Zyb;$T|29&x)LHW1&2oJO-ek}%;&$w{LfuR{x6@%p?Bf+9%)X*7_nwSSt4|lqX z1Z04QNPAn~y!-zjsU8&pjr@Va0bETsA2DcHVhd8j08tqLX}6VffC`s0h#Ig@K1k z!D>PMt}Mm~wDD5V;3D?%(m2q7DYRQq%DQ90SqBDCQ?mJu1xnQlYy5vX3$DsRwJ5a7 z52{6>9RX15A6mD9DpYWd3$BKtwd@;|TGsfar7s763mYQ?gQf2u{+7R>`qcLaf6Esz z;|qVwYX$}e>zn*7kHG>TAbmCKgZwR*zyfdhTTX%+=A}H=ANgB$F)%P#@bR~7VqjnZ z4d;e{iuxOo&AvDvWIh&ljDf2mfsx^V=#NNHi!Ss_@PAPkkQ-e&B44v-F}-HWVvOib-2GGZQp5cT_vkW8Z0%Tv$W^) z>irBH3=Cn%T@M&AFfer19{GQ{)AdBJ>w(VFJKd%lpkBdo*Bkew9T<+gp2-IFBd&G2 zegMrqbk-g@?s^8a`Q^Cl4R$%uJXWXc7lceMXwJa(#w8_)%nyW29z^Dc9!SRcTc_)v zu;Z>hCLqHvce>8#cID`Fozm<2q0@CuuWL`han}i;=3{5+md?^Wouw;|yDkCksrfIe z(*Tx&}J% z1@1C{#)m=k-=I;tUe`5Q3<01n7IsgCv zKMp2Ae9(X(q(&BN-dphh|9{pj#)xvBQVvUSPo(T2jVr*P#)N&4Os#iE+8?`Tsdfh>*QhR{Er!U94!35smCh^ zhHl%G1_y?{;9}23MI>}LXmB|ocppe>hR=CW)9re%i;6&2KmfQQ@?s6RxaI+gK}-k@ z2+nXpC=3k%Pw%_{4LUa;;en{h3dlI{!hs<;!vdlZBHFE+07`X?85&^Gs}Rw}pwX2q z#taD*LD1+(78f{Pf-^Y4Qr98svjV{5WWgB>=N%X_zMOMl_z#r<&r)ZAD&ydcC+EOk zfokE}oAAnkAv8GS0z^3D#5o6s&=(6#A>*Q@A}<{OfhJf=3SaE}3u2UJyw=G$09I5D zQ>64l2BOI4#T1Al=NDVRj1sFC_W%C>e_hsK3$llyL@Q$lSg$=)Z;50UL&k=44h$I+ zK<-6uhCxF4G-zyhF=%%3h4Y{P|05$|4Jptfr-h)10}TrY{1=UBaDY#$utO&RVdKo8 zNIe|d{ifTMC#>~Acu9VDtw47v2YAs=TJwLVvc!g;F{OT89*hnRKMiWU8h#qr$u<1U zE8%JQSyXE6aEy)D?X@wOI>yH3=I{Snz1No`iz6`LzvvTC;yc1)!&S-(Qs&>m*6qsS z=Kq?tgU`*uzx6=r%-77_r5p`v4DoT`MJhW$W2i4YOu+s7Z{gjxpb{tSxa$qjmc!$& zcbKIe7{Dts9{j(a!BEe@5Z3AXqOy6z#=Rtku z(jTDFa#4XM2ZprP1Eu0&84aL>kZ>N=fu&^w_|JMVu1pZ$S2n*|N1*r}T2oC!%Dgv4b{E)>Q_Fq(@36zjTWk4dw5G8m5 zsI3IWUyN@D1pgOpXaE)4Ai;p%)&-!J^$m#ipxzHutej_%E6P7W?r38Z3-JBMRZ*h6`v# zK({GqF=i@c8~p!T_m9Zd|B;Zzs1522?pa*npgww}^|8nj z<^}}@P`wE5e1!$W)@(p$OC-WFKz&ZIRY<+g*GAy*6BPh;U5}MWH~(NN5ev(B01ieD zlnyGmn*!>cfx8=^*(a275lVbvi||DeCBEQ6^~Dbod=Zvq1)25+#Q?ZX9$Cs1cX;84 zMh6DBW(&qT0seh#S)g&_H=qR}k*@{2d0#X-F!1kV>*nlYacq9T(R!ftLKvj*i91X^ z^I94}^YA-FK(jV-2B4Axl1#$0qW^DE0Tm$suNnW3v_2kLBIJ-28vq`cz1Dpr()xa6 zDQJ=m)R$;bW^fM9aJcNi&>5p5^5Tp>q)g{;iDYD82n`QE?xLcia>0S2vqnXuGe$)L z+D!tFFm}HHHy^>HjGzt%Xey!k2&mx)>iXd6B!z-HQ#`FWx=8;^R7C!Rm(_t*PnVd2 zrXd5en1aJz)O-OA1q;}McFLCG=-og&KVX-U+sO%qbaB8Fq&DH;UJkr}6Ao&)hyDlm zZ5mQ!LKr|DoEjAshFDNF)$5|7k|A&b+-^e3O`sG64&)c9dZ5`HP}e3dy734o?m%_U z4$#Ebi%30CB!(ORZ?5HF?yUXLS^K54_DyH)pX07SxIk+Mc*DapOfESvguk?5W?<+p z<;gI(O@er38}t7UG~kOmChRR5cz-?6LdlAIzNDO11K|y z^6zUgeg6Lcf8-n?2Fno;DMYp?VfK4{q+y9E$Sww`k&sE3$Zp>coh;ps*1kVFeLvJl zH@p7eYCf(J9{gW40n|6R5!re>64C(zS5+Vr!109Q+Sbtj|NnQF@)-XI7jK}p{c#o* z2PX%HESCSJAA&g+3(F3*u zG+|(UxYQdWlK~n$40z!W)mf?pu?w_>GBkAX{P-{-XUiNO5Ed$A8y1FF~u2{)5-TSs#XU51W55^S5jS zbsSv(yaY{hL!1RMD+9@_5-DVv03?}Gf$$d>e?e?n^xyT%>r`ZwHpnVD!e2=L{Qv*8 z6S4{oG!^?nDin}ah#;$A4}ZZ79*uW>(d_zzrQ7v|wd)7|o=VVkMk&w#G7d;_+j^ix z0pu9{EP*VBj2#!yhHXIO0YxCiu74l_67XWm4Nwp_*ZyHBQTSi_2jYPLrEmV1e#uCI zeH_!@Za2?<6qQdgupxZ@-=fA;= zP%TiYa5yY{AGpr~Y7m8iG=bVf8K6nmFv!g6V$k?t>w(Bp!HgxK+zpP!fR~^V_YBY! zZU(5Wn*kb4gv`|jya1&PaG6}f{zB{X|NpPEnr%Uf7)q78ZGAxV9@oM7_P=NVh-2{o z`hSBLS)ibed~FZj#sXjGDDO}rlf{-X=MuPdivZ^wP$ABqQ2;7XSTlSsfx22R00LBq z4x(76-A*#r2TR;KJ!Cq)L^{1VI=v*0I~jl) z#ho0T9xR<6Je?j2ogNyU9y*;K2Av)jogOy59R{on496Wr7#bKDIz4haJpwvCe2fnq z-tq7M|NkJHKq)`m_`mgWNXgS+VaVUQ0yK|ZqatF#-#QnxHV3rD%KAHh3uq|HvQDA1 zPNJ0SxSPU;SOXZG%^fSK}$*k{)d6K>R2By<%rt>T7evY zxZ6iX!1{crn@lZxX9dUio391CFCP5G;?(@ppfiA@mbv*MfAdd{8u8|z{PjX%ogONk z6(%o0(b0T_B`*GOK)0Jl!2d9n<|7=Pbuw{aAH*Nt@e8zu2ILb+s0D-efbn$4%7le? zf9Q5L0eP)dquX0$2PmX2Kj`+=$YKl#{14tpWPQBT*`$;!J}wq48-3WaTBB6B+goK9 zSn;FIYJ<*doz7qlP#@G=L;HL8G3$dxTHU2Q-PI=AAG;48e982AY4?fdhs>XsbbD*W z$Hg9Z28}#1bb9L;ABaAz?QLLvqSHr3q>QZ_Om_zxbXS{nm+~b`jJC6o4t&r7>c zLG=5mh-e=y(SVrVeWLrK_6Lah5c&8{XM=8Mjkr!{9ngxb!`;;&F7was>(&=Ks|`xo zp;mVX>sVhXW@WzE8LR{8R3k#?%o$L5*Zjt!^*}IxOS>ooLwAG>C{xscm>xW>C;3|n zMHv{HEf`9+u5SiM@6HX zyZNU~XNZbIXNihLXN`&oXk}ezjfzcYh>F2+7ZuQe7(|M`ECY=Qaj_fp`oEKf>an7I-Mo5n0j4QOpdvzn8+V~&xk2uKki`g|NsC0-<&xZ z`CCBCjX|ln`=IK71_p+M4>%6KU~zMKrULgnB~mQEiP0Z^Jh$kF_k#ri~j~oL-cZiBiw~tE1!B-sLFEu}6YCgi#=`7LdXwZC$vr`!4 zq3)wE4MFxmZ1w=z?4ly@Qd|fUtm>dp%@ZORsx};*&H^O@Aa@;i1bg7+6+s4uZ$}=K z2y}<2_#AhV&;dDgKWK5Sw?ga55{`o}IGP_Yb~1K53w%4mRH6#L9WE-W z-QJK0(Fh29u~7-szXv(p_<-@-bulIj`9e=`94+ zW^CyV;sTNG3yqIJDWNlvquX2I;0umUM;@q?kbR!T)ElCr(cz+^+3hS56!2oZBEsh& zAAx)i8U*h47Wj6QrKAkx6y09Xls*F}B#sE6gal6abUHhfFn2nNU~vI@*qdUw;58q_ zPwdAX!Abu(Gb<>!gIxqo`Q8rJ=R18=WXjmO!8E9}04Migh1N?Y9^F9#t(Q7OR4htt zyMq<{C-X<`i=fcx1Qj{K4&B}kpaN?u zKLf*YXYk2N$C=q+p5Wkb0qt=Gg=Yyr1B3BNNci$^6ESoIc?#_N&JY!y65Z}V8K_5; zx`Q380~Ly;x_wkEK4o(oDR_lb3T9TJw65oq)3nfZFh!ygB|7#0sfX*3=H68 zv6c_y4eTCJLGgeFg&trh$^){f9sr+^cAS|5=5nSIe^9yxmpz|&iAd2BjSoR5x>+BD z7d8^0!X}6pR4Rf?IZ#PmBG&CKaPTK*^Fzi?51sB{h2u`(MiFRzdKLquy!26tfE19> zC^*i{&A`C$l8+Zu94ZupofMD5N#G&}l)#E^f(n<$hYbH27!E!XU_NDhsjEaq=-@++ zgD)hQPca_^6_XHajKNF#LAe?Eno2B(+iA49==7;Q;UueHP&MX0%)B+XcAv_?LGZ(9X>&GQL*h7;U z?CiUs_6g&~7oFZLAQrQ4^H1jT;{PhW9 ze8B|MGzBCN^H=jnsJ zrCMjO!^^o`plIVORtA+q@3^r0k`L@daIDEU9t3SdIrxIL`$DI80*F2VZZWVlKa%hC zj_LMJa60&crTHyi<4-w828Pby6p)Ac+d*^H-KUy=%JH{@x>CoTL0vtDE=8dCl>|7C_aCmx4$V2LO=gMYh7=!$L!2IG@mCl0<~Zhp%QvaIWfx_Kt@ zZ@X%JwbMt%q09ypS&p3W$m;eE0kuMz4e!@*YqY0bX` zii7yKowa@mN@B%oAT!@`Ak6epk%1WMX8jUm7&r~wO6v|$aRO!iZtsk=&Jb@%AiJmt zbb4pJ^k8IQD9zyCcD6T=$@&%8SnclrAcLo1H5j6-Mnwc{_I0SSttUZ|$lsF0$iTqA z-N!qx(?>-ITuw9}WIp(U36#Y_TkF!Ae+lw$yV@C|B2((s>&0ySvRD@spCJ2G5cYvQ z0dha{AyED22382Mulv;H7yCgA(?Eu^gAHeX?b7STZvC=Y6J*Lgc6e}Ln8I%T63LXG zpy9+5%@6CF4{{%T!LI%5xQmJosJdi+-(8*39h|{joxpt2`a0NW?K42t<;fC>?hqB7 z?*HA@ppdLC=&sIi_w20BD18UYq}{;@-QFq8r!2iQ_?zB=cJ5nC^S8WXV_?9VD7%9b zpy?7+ItGE0CF6^Opz7p^1b9D}NeQT70Bg;6fRf=XHh8kk0r9&)d{B$OMn&YfqXby2 z3?vqu1Ih!z0q`6U5fIqzop781+?H`p3Fr;+E(pqEdJ%wPp$N2}5z^zp0Lm0EH9>~C zfO2TJ7yd!rF`M? zt8O0^hl4K#m@jsF`*e#;1hu;kzT^P8g@4;g{%zNoFM@NrOj&fVC%FCzK{(F%WS5VM zjz2Ux!#ba*kgNcuORzQk+kI5@AeMlO4^ZI&Y7~NuFJA#F2tZkmf7`jsFZj2e(|!qc zR?8(&GVSzs0Cj%F85kH|ma~BTr%=KTX%W0k2lE}^tso0f>hWP=U}%2C-x=(2oEcQq zc6)oYo-A>-ep##v%1jekKtTiUGIx7>fbtXbA>^XvcDJ_&%oE-YUdk7kPjz}bbbEV% z+<2TBw3DIRI|8mE093faA_E?YI>?qpKrBIMfftG}eeg&GN8V^pxoCb z2r6|tK&I$``d%6!Tbw+hX33P^hIZug7(t!@6<#ls85tN_L3tlEm*b;&cCqMO}5DhApi1 zy1hZMqR@I0GzivxqEsEyedxY`+JOLtAoHp23kP3GFoPn*!rEJ+gcsVTdC~2_@Hzp} zrH}+gD#VwdlNq|b1whdY>W71dPQZN`kT?MkTRY2?1Ve4O0BYNVo9N&{JpQ(nH=uoq zvEZ>f$Uq&ao(fUX=nUim#d2pQOMItcXP`h_XSPkJvqkI<(AW)FC1fZ^3e;(EQ857x zqrT<2~H|KHqwv(_5y*wDo_9VYjmYXpxa%w;NBlw}kfbfWQ}00t^hV z`Mbk-x}7DouLlLZxXA_XzJo^D+zh(CS-P3O-;_RSe5v`NL-P-A{;7w&8XtjHmN1`U z_O@w#+v%rM%F_D3gdNnCV=0yF1~*grTiIVj0`>R*|NkM5c2O|_ZIA+ugd&2v*OTFd zy90xvrvt-9PX`7sF9(LRUJeZL-VO{ZJ`N18eH<8;`Z_Q;_&G2v_H$sk;OD?_+s}bv zm%jspc7OxJ#sCKfqd*4+lOP8M%}@shj&KKtsBi~{l@SgMijfWsuOl581fm=mzC}4O z%!+nkSQ72P@H5(h!QS72A;jN-q1E4kVV1uG17tM)%o))6Y8@ zJkVW1%|}?^av(cF^03W+Ac^KTHo>7`;oUAO0o?-K9xUB4Dj{CIK7zlyGh{k_R1CTc zc)A?~x=(1I_Ummi`2X+!f97+YE-EJ7t~#AQDlXl=8l5gG9^I}c-LW>{VQOE4P8SuO zZr1=%QP=J3)9Iq3(h17>z9HaA0MHacM0c%)2WY4VD$<#wBGDP5A_5Y#Y5vV$tiZp` ziP5S1^yLT5hdH`WGyeedFFyb&V*UXV1C1BCs3<6(?`G-X-*yI6V)Ji1aQVIV&DH}Y zPAddTeOEZ}w~8|}Fzf_nl1?9$knR)RE-E3HAAlNR2VV+!C?5xpDJXQaG@_eOx)0RC zb36D#po8t@EKterqatwdr9kroevnBeX4W@~h4{B+f!+8N?5e-bhdH`W>;+kN`DwS0 zN{FxWd*-v2^_3!Q!jR4}}?LZIXj z)bGs?_^oe%#>a~_;J#=+3~IB9F#pBwH*hd^zwAD7`Kk5|U*-4RAioJPg9P4pgHpmV z?Ss}YK*L7-ZJ_fKv|m`~sCe+VgDM$Nr34xp@@zd&s@3hHqM?1185|kbFZi25n=7@C zcDks9fciUKj*O0Oz8!3(-(DA6A1w~nKFaK(qQQJZJ4ZzZ6n)k&ij$$L!Q;0G^)O4i zb5ue+t&bMlXoFl6(&?h&(ES3T?6nNS@?r^yQ3zR3w0ED=20O0#2UD4OZ@}Ljt5>aB z_5D%ztBY@4CxDf9uyqA8I=Xd;b%&^gfJK^LG5dC~b%v;fl-}(QQ33T8LRt@$#DOI{ z*qUopLKw>ex;z=(I{03DbRW|`3hlFl)>*oB@S#$mCE2~b1owZsaA63*kU44{F!*Ptnsfa4AlpqB4(SBUz<2VXGv@*D+q`?N1;-|RjO z&8r|oJd}U*Ix%)K_PQ~G2IO5-473k5A7BN|7r3aHXdhz*jjD7y3Ut<5#K(2U+JI)G zY(S;WnKR9AYPzSWfNE3Y1FfK@xeX|SI$cy;K;=lMi;7P$C@F+?uTcSQ`|Na4i3#g; zQAsJ2gtTw?xBIvho=Ay#O*zKdj)5+B> z(0qucQ=pTpnStdvXrBTD_^gKJBQ=LXXRk1T(ra3CO^F9XsTGL!VklJt(LM~NvLM=z zp;QV)2QZY1Hy>kd{>NW-;kAIS3=9luoh)8p!LAQc7|A9V468kr_(-7EqfJ}Lqb1z^PqpzsFG1A+{7QL)iJ4UWRg zkGfBH|Gf+<7C}R*F1;q8^S>CFfAsR~1Rb8LeMtLU>w!+o(r=(4&t9+pP*1fUEn&O( zqV;I$uhs)4+`S(EmCrJt=zjT{$@neE$<4=@(*FB|_@p)0g!nLYmZ+$d%2|h~$ndv- z?!D;lQ2}it=)T6kor!Q&hkv`B_VLyOb^IW; zj!w|De=>pMIIYuX0@F)((0Zpm;FQpHNb7LxTTo0nbhxMp zLL;Cgw!1_{0hAwGZ-WObA)Y;JeF8i^EYfXmd>gbb6r`p5Lig$JqnBTRs=`j5Hk9CJ zpMWL&OIL$D*3I9`0**#d@y>jrlfS!11vKFm9~avxXnY_VR4Rbl%%BDUm;-8UojKF( zqrwv09it);-u?67djZIZdh>6_QcxpYfPb3+^Qp$4AOABj6tOkF1kubt8~=moBDTi& zAiDeb!3P5I+y|o%gPMvWpcA3u;=!RDeYp9zK(TS_?GkSOZEVaZ+?aoQmwrVxt^2s~ zrF|e{zJH60kM2Hn@PPpL!NZNeH;6JY@Hd@eU|`s>eq~(nZwq5&{t2d;pfod-W`WYIP?`-&vqNbPD9s6_xu7&Rl;(lbyil4C zO7nwg=AX^S1U&d#%Rwm^v&WgD+V4w~2urJ|G@wek}m=1L&{;w5aMv_7XUjz+U>+{j2+U94yu$$p}sc z28VTjYW?>8LW$aIQ2cy5$Xp_0e5p&&7&=wnEi*NvA2jjAVSL!puk?QNKZVe6{w8Tq zN%-&JdsbscP(N6Jf7_jIow*s)9T@nxUFsH@nlagd!T4(T4Nyb+;A;txT)zWDcO;AP zRq$F2W0BU3*$xcGm%9%gd?CQU?H)+QbO(m!gDm{p4s^4$f(MF7=+qmVg# zW0t946Am{YWTDInAOlccfp9|iE#spYPJlY#Xtxg73C#ytjE@>0?!M4`kmcYj0Ym_R zJkP)Fj`-bsVkW)}hfyBbq zZXIwufV^-OBQg+<00jd=J19UP_JdLfC>oD~3^?5UPQjnQ^%ZD5{vS(`NB6aZub7M( z5v~F`f`40$icq)BUQqmY-!T4*FcspWzs5E&7d5|Q@#;Qay0`UPd~Aslr2T;>X+DYj zejz#%(kz6h?`|Iz&_X592E6VN6&}!Xs+Sy~!41_6(85jC957h|CM&>X4VY|bg!HLx zL2J2-`M%#^KGJ-ErIleyH$%YTz+N8}j>eZDDP0iJ$_P>RM*Bqfkygg;hus|A4lDtO zvq~~%tao5|ahr*O!T5G(?1Ny)j->zB(;6*&qe|twJ#3gyG+Kf#`zjUU-|i@3%MCio zO5T>6iGiVnwKMj~YxU-xpaWF+%03w%u$=jW0kr6#xw3?>>=B6n0yHW+^+2cVlg`*b zSxo;$6+pWwnt3ZgXX7^W)_|!0qB5Yh-@GYT92hz|{)Wfd+vFqHCST*wD4{S^S2`2uupSO(7l2Zjue0}c!?UV|1`m*hJzyl@8{ zmJJ4kKtO@4^?LV+svAOYd~C*(j99S{*K7wTyjG8q&pPb-0AfMxmU|t+Vvb>kpl= zD|%aN{{R0U@LzOIg9F1i*AbQo%m4p(f9NbdU>W(3+M3&Zgy-=85*3yg7vbkAMtU61;>lP6 zN&cN_vI|D<~4hDvx9SjV&wlgs3?O<+}pyy@NNqO1JhOp2C=OS3|d`!;IYw3HVPH_(!oVQ1g@J)%3j;&PRtAP|P&Z%O#lUcG7X!nQT?`D{b}=xl*u}uG zU>5_!v|S7g4|XvyyxYaV@NX9b1J`Z_2D#k~40^j680>a4F!=0dV2Inzz)-ZCfuUhH z14Gwt28LC;85p+hW?(q9n}OlXZU%-MyBQcB?Pg%8*~P$+w~K+{)ouoclwAxAF}oNT zf_5=5+yjO24hDu#I~W-L>|kJE+sVKnu#||hgwv&P3(@qA4e>)i%*mf~62<&2DklMw-pt6gBL2nlWgVinu1~1TA zdJGH|kJ+2esR|C^fMp)g`kiHMu0es8T^gwOA9R(l@anEHky- zKg}&OClx9RQ413AO)MzL%uB}*hX`cmrR5}+q`Ky%C={iZlosU`E2yS0Ffc&HQ3Ro4 zo_U!iM#vI|dPaK23=9l0AjdE;FeHF>P;dk=FmMPkh;S4$2!Zx=aTG8xa2PN!a2{X~ zVCQ0B;7DL#;80*-;9S7K0NS_3(ZImK;lRMa`G7%yosEHkBZ7f}LxO>Ua{_}fyEOv? zM+E}|hXn%z=LH5qc0mRPjtm9{4h;qd&J7F<92^V`932b{93Bh|oF5nj*jX4DI6@d0 zI7ApkIUX|zvuiUjaFj4GaF{SKaGqcgWanjI;7DO$;80;;;9SALzyVU-NO3>-QP44gX{7&v$s z7&v+u7&v?w7&w102(U9VFmMDhFmMPlh;l4u5N20qVBjcXVBj!fVBkE$Ajr=0G44j9c@t?@Rz@f;%z`2Nl0d$Q8MRPJ+gNB?AM8B?ANJC20I-GB9vxGB9v%Vqo9^xwn&nfy0x5f%6kI z{zDlUI7Ar)IG7kA@n6coz+uY3z>-)44k_d7&t)bsh5F)!7&wnX;~x~J$_xyg%NQ6qKBX^3>^Lp44nU<@sBJ2AB4s~EdMV=%l{9d@ej-Y6QS`B z%l{Xl@ej-Y8`1LrM`--x%Ks;!@ej-YE79`*OKAMV^8ZX|{KNA9O=$eX^8Ze>{Qnag z|G4u1QE2?b^8Zq_{Qndh|FHZ&6&nAr{C^c1|FHbOm4Sf+Isbo!#y_t7e-;}5u>8Lk zE&soT#y>3o&xOW6EdSqy#y>3o??uc1f1&Y@EB_yc#y>3oFGkD%kD>7o%m0(1@ej-Y zm!a_w%m1Lp7IOao42^$W`TsOD{$crlHCq0E4UKH)xDYa&R0f&eQENI$a(t073gfep_Tq20;eUP7V$Rb_PxcHU=&RRt9bc z76u*$W(HmcCI&tRMh4LG$jrRt{31|eN+BmTFTEr~Avq&8IUCgWfr+OimL$SNLE>eJ zIhiR6DVfD3iFwJX3MKjZ3TcT&3Q36|Z48-tX!6PVDXHML6hu5Hvm~`BF-IR}8%P#i zPq9LAY6+5_q|6eS>xxnfQWKHoP|btr1UV@;F|QJ)DZdEe+~UgIr2L#>xI014gQ|mQ zMzXiKB)=#%MIk9CKRFww2GqQRxfdc)QdyA7z>txen37tgkX)3kkegYYn^=;Z!BCo) zotIyprvQ~p%SlW}vI7>rP+54mL&B*%GcP5-T%kC#Div-{a(-?>QEG8rQWJ9# z>eE2YZ8W*O6b6R8)KpL+NiNCE&r2+-WKe)MoE5+gWd-N_g36-I^o$Y(LrY6jT_Xbn zQw86|qHG1nl$_Ke1<*7#3j-?y8v{E72LmSq7Xvo~F9Sb=AcHW2ID<5UJcBZWHiI#P zHG?xlFhep!F+(%MVur&Ej~PI_EkXVeU_ga}3=k;90D-~`5GcX`fual$D8O)%;Rpk0 zhMb9knE~VmQ2ya%5M+>KP-HMm0Q!zjQg!KlD! z!05o3fNVd=R*;P#+dwvfY!PD+V-RN$XOLi!V31^xWRPNzVvuH#W{_c!VUT5zWpH4C z%$iR#U@zB6%tk()Ve$;r0-zrONv^qx@2vyKzU^m*c#aJ8r~ebL1y%r@>< zSiJwX@?Of`%C+>@imj$OGd4M0JGuL|!_Pfy9_QRJDvi1CdY<>%ok-h@tJfIZRI_5Z z7uXPZ{e)QJh5cK$#)LrNKm+sv$ZwHeGM+a;Crkgy+R9T~6HMB*mjbAel}N*MMm zeq^xwisRDzUe~AIu+2Skamnv5*Dhzh%yj;9Gtb1WBYo|K9SOIJby-}V$}$_r`*5u~ zS;KqRrjRvYl_Qh7fB^TtHU*xOs#`O{H@D_1a9&BG}6}{`~oVU!U{7?c!JaHYB`Q zeC=A@%Vz1nncy!KBOsoL>qo%Y6R3+ZhVh;@x-k>Gt<`Y83C^=Du?2a+}km zD<6MszMGNPd`teU*uKVSwVmG{mT$-}ci%4YEQ+I}AdCP1DQmX07;Z+v|MOD&GuI{k zxcomm(e-?eSj8i*1@A8LUJq$z@jJAc*)mZreff8>gbTjznVz@H^X$B`F0Q*1b?s&v z_kD*C);G+9*X>z(bl&cJ_2)Oazx=<|K<3llyoqMfg0zK?s$!aknE()U@TdfzkYg31s_LutvTBr~JK z!MfTkM{`nKzm(UuEsqL2KF!geqwD4g&)tVEcD;E2>-5bpk9qvJuJNr~x3y2VdER-M z8SQ~Whr9%oZw6m3_);;+@qJ?G^>un8&$gOh-#SBPT1zu)@VLx zYwg~4$Z%2VM43*L1?&@k@0l;YtG9)(^4>~Si}#ys{#Sl3-skZmt5*7`qm9li(UvKjZHc%WlQ~<;ioi2*aN-xFMU=J=&w58q5a{p)MY^`s)% zXO-FawuU&&Z}E?e5aJV)R2JpGRbZhq+fmtedG_DbXHm~;R5?$Fr&wI@TfA-}aq z5{hS*8z=nTqqY8_%G!4q?6>GP3(e2j#M!o5#pC2F(clZ3Zgsyi%2OYGtF+hO?xA8< zBF!mlswKqoX-}~C=3bAWf_tfzhVSbV{(XM-eE*@p|7s>)xN5uL^qF5~UCq1MC(fz} z*|X6yb@>YK8O_#QD`zO3{I!*{?qcUc^D0jYjGbc*UUsp7~&WvY{uC)4H z83sEch8a794VxECNLtr@F?j1Fv7=w^hJSf~WXkeG@2^g~`R(N%_H`?IWwvMEGn}LK zUaR$OW%j8x9tD^4rDK2SX}LXoXm&4c0sGPVGX*$25JlD zYwlQg^vTcp_x@U)`FySV?~C($vR5|zjoLKRj&uH|dW)9T8&qU;K8muN8o6o76_*>b zZ+TeY@%2J>pjoq9Rr#jaWWVdbPV+x|cxp&)%f(3Is9e205 z#VRwi=U7M`u@^OL<>S+En&F?$yfUQjz^}^C#?whY@<%NiB^POp;tE3r1 zo<tb(<#I?^L-^@I&-;rnTEMr>gS5(Oa`q?|g}> zJ8I4u{I1NxM;}o}Kts>d^hw5?{AwN}l*+D)fKGcj0Af8wDr&6bpz%*@!9|{1d6_+ac!r zq)PmOo3F)noqo%{Nhhs${@}KvZ9aTC9botmR8*9tn=UIT`$JLTdSCKzGg{DL*-;Sr;1YxSgQV>I##_wZ%)fd^kET)Dn1_3_@6 zlwHh9X}y^b(iKi$_Yhww>ggvR>|I$p(d$q0J^$03GJcb`NBJzhJHvOztRrsgKd`xf z^K)=LsM6%(VszHAID+3n>zSvMY)`jyK*qJ;kewMC_`Qh=B;d6cSp-*bIEguBK z-9AonfuFAmk&;TA$VZLdf(IYkM^HBR(Wbb%ciXkNtyoXmD1Fs4G*ULIKF58`L%WPrU_fjSy=gN zu1fya8N#gPGre{j&aS=qd6t1`XD@racTe0y-oDK4^Zjq;Hgw(o?$q7n$9=a{4ZX73WyeR{hLdvbv_? z^*V1M&GkZS^4F>!Tf0W0@#Av+*T&1rLQ9u8+HYTaMDxdj&(2m0SN*M7Ja_uOMHh;X zY<(`twq@UPhi#o_o3``ZKD)_!Bmd?ke$S1esofhgl&|w+e+%PwF@NaL@cse75RNi|cdWYiv2% z<{#Z9s93&-Mx|Z$+-OfgD`>Qi231?0ly5Ez7Ux&@T>608mAKns_(B| zemvcoqo`CZ|J>Pv`n6L`;x%Oiq# zZ@hKL*dO>~;ScQ(N9IrW&@xbp@(-W)=v%&L{bY`>$7`26T$MQRC)e-7d{gc1%DqP= zrp;eauca>O{&!c6f+N$U{x1UGUwEvrjS!g~VZTCo+XPv|%ikAvW}Pn+;a_oT%|rJ3 zeJu%x*)A0NBQAJ{Jf`+XKXCiJkFBn zvtY-~uQT#pCj7trr!FAvowWI_FkkhZd>Rg{+m5~8uw-!ukBroY+l}?t(=8`fT+;p` zpEsHLyP9>{gjvlU_WBdwMoiWWNpRU>$o_-p{t92YUFSb8dvkx%zs?b|tooXs;$D7V*MBZkGMXkhud8C8_6*sZ(-pi=eyPw~=W*Zw35(mpnYqfA*%pq!s(t zXt@bWS}2z-Z75AUc0+)%D&gO{^F^~y7N1i3xFpi4$~Xd*@r0-1+fb zubRu97mhzt0xR`@n#fIdytPp+c>lFouU{Vfxw&VdD|Zs#2TAr!pN0DZXWQI1Q*SYy zIcZwJKRvyfJ44j|+OFmE}Qt^vGBK;=)aLK zJ!ja3epF;@%5h1wyRzzB_p$RU&Fd^2>#xPC9nRa_SyprH-Kit_*JLBPHZFYlL{8*$ zW916(>zSv8H}RFPj#+qUr{%Fa#)f|@=RBP$s={ryz$`CNrf<1eTHB$*TXs74@^w6S zUY_!yk%e>WvmG0&J$V<)>1~wuu)E}}8=3d}@VDD9mrYC$lwY~wOw~D_k|~Kh(>}7X z-UH?Ta(07h@AwjRW^xAZV$VGyS&;Rm|7G%0(|IXV7xUfjFsi(>W66W7FXg6Ry4k_G z%}2X#W6j>@J9OD6?3bM=yImk9YlFh4&pQh}TJ|~Kle%@KHuvt6*I%w|4Q#&LdR#bX zMp#PriUWU>epYp*o_r(17~`MJmUHtLzio0a2k%$wJcScJnF4WF6C9r`N-r$3VSaMa zo8^l8CEnJ*OS!gYn{Hk4%W2b$oZGukUR$&0r-RXroX4*BV@mH_<2}FnqHUzwO@lRo z_ZX~BTn}v6e<4v!am$yjVVjRApWiig=8nBf`8Ak!%!_C3;5f!}bJIrdmn!=CHSG}@ zK7t1mWmj)Y)3q(T<2Zrwb|Kr0OA345Ulq_Q+}PU5wr#8Y{QXaszSwcaxPtxUB7TOS zCet`q^gZO8AyJ>jyPG4|R(E1b&h%%=F^t*SfnQ{D5|dg}4Q_l&V(`mhoBCFoacOlE z$Cra&`HzIBZn$|&X#2~6?tMF6{n^=3lXO@1zUVDo&z{RQpMPKRN%h^77-6+F@Z91( z3?)}~8$9y5ztqw8#?*k$lS2)ilKA9hP?YYePCD4+P6EJ6=v<TWsrQu%t8+iIf4 zcf^_>Cvyu;NwIF>&CRl_%8FX?gzr4}WX}I{xfs@|*Rs#s_viVm&ina&``-T)-@eZ|VME{5 zYu6UPSa@-AoyCnqNBr-93c9#E{o}Gd;pz5U+3tF5GV{K{t9@%dm$Z)=v)4CAma2sN z3GWZDNWULynAi8-EpuPx>BP-Fd(xVvRP!$_2+DY5c7~^X>TYg#W+f)EouRC1`bT$J z@7uJOTSI$G)TEfrSuBUH|6jiK!g)iToAbJ(@2!)aopNZyo8(VwrCE#TFy&6>n#0Mq z@-?5CeGz+lD=R~|@YMZYO^oOfNo^{C=H@1=KsEX^U7%$pA_PghI4kRbNmGt=GIF0cIdx{Fy}H?KwA zak$T&W`4u^!^%DDg758~chr5;`Few`|6lIi`$^{1u0vhHn*{>xHS6}Z z&yYFqbx0^M_@=Tz#g~H1iSHdJ>8-mSYQFWEh|G+w*IAoerZq{=h@8!^QeuPduiGol zPS1K)9vk~VCr9Rrqy7D}QGD~Jwx}%XnJ@Zy`6f4oeJjfoKfZXl?8WB`&kx^gR=IU_ zQ)+DNx>YW2^Izv@pV25Q_?u~In7vLxE9yNrJExA2j79E!H?z;jV%aynEeI+6lAUU> zTkG@pMTUpAb;?XEoxr}pM1209-+V2-yHr=+tF+nl-lF((<^QY~9{U`RO4ml*)3W(c z>CgHysm|q)zi#{O5Se3BM7tP!`F1-jQ-0CB-{R&Wiv>q{6?cE};hMI*U$}SLxtKEy zoUYdmJ@S4fb(cR3KDBFiY{=pTIielC_SYs(kzW_UZj7^d@(+u2!2szNfnENTeixMvs3?OFRytP^(_1}r>frtixkF*>lRCN z&3|OP{EVX3p1%oeRUWS2Vt?V?e4%FDHqK2sCp}bFUkDa`^}EhZ^HFMfhQ58}H!~HF z?XsNGB`iW(rryDOJ_UL7ZmvwdSCCNm-thUe&;S1YJ+%Mog^4w1PA{-+?lSu|Ya;vZ zje9~WRxD4ov~Hf^tu$jL7w6VrCk57>?udUM>+kj@hcEZ0{n?U3d_A9|gq&YFDl;9- zDL8ne+_7Q0*>(3m-Dhk|8MYqXBi+(c=-4Hd5w$zox%|b9sGOTOMRon-NlI;6ARf%cS=cm#u4=pihnV7CQ ze}USQqw9A3y*K~owa;g)&cFCu-LNuy&&*9xe>ctNv|HU`QLiJTvcZ&H^rM`Xn-RNV zd9g>q!!3c?7rs`xHJc^JZYn?h>$=~ihtK$bp4ocl!S$Bj_0xpBw)HA=&s|V(qGh*Z zC$n;_o0LTkyP>H45e+`R)^z_FPIVzGnL{go9q>sy-PmYxRDOo?m$=P*%jd2Vop$GG z2!r$=f5WKjNl7!$RR-VieCRJ8@T)GY`nvAaq%$&CbbGsmO{VP*lHdJeBHMzS7vkzZ zzj04Jv^K|MVqR(R0(~nXvlq&o>?=7`Lb3$yQ?;ipuYA(K$7AV|iPC9%yR_6lo!;}{ z^@ZMr2manmy7}zAvdEr?dwG{%_@maV4%HKp~r*GRohoGr2X{vpZCtzU&qKb;W%KI6aOMzv)E#Xb{7ZK6a({uwBX?dYo# zuX^HZ;p_ImvR~)A^~p(nR@^^!+Pl27v1@DLwmD!GWXlwgY8EFRVLqoe-}KgDGZTrG zjE3R+TMVXN-E4F@`<1aE<6V{PrxR3H_Agg|Z}?A5H~OSt?^M)#Z}YF^pt*`v21 zx6Y+K-{RN3yk8SuW^ZlF$tr&nonaWnl=<1hG--W3M{=&HeWLd2w1k%j15=k>zmk%& z_i>sM^RDyy2=n1k~wL73j zxm{Yn!lOj@?E3i*Xrj!G?i|6@V#*RqmAd^ zJuY5)^O?=&nCJg)W*@Fnqu{-Y-^-*#Mnz1pnn zRc6fIzrs5{{JD7P_`m5n^Ztjjt^6e=p!54imGsYfN1T4dtSkM@@Y3Vcq2~GzEw*YO z-NFUGa5_%;dRlw&_ui?$zWM(@d$S@o;l?iW@LPX6`EJ`iw!ByPbwl+IRhmQ~gKhpEy~4>?F6~$1^VMC1=}mozEY*EOm}) zr|!h{(;Oz`{wbfN?W8yPrH16RWp;C>r-ZDYs`Tp9lm`t*=kGcGV_x0b^K&eOr_KFU zxp2nTe3hBytirPmcYDqHe6hB-)6}5HJD$Cd_hDTB`R>fFhPiLLoxb1hWbth3IHtU{ zc~(kCQv~1s2DuH-8t>e?*fRM{NNeD-{9~iUGUM0!OG-n_mTtFqT<}Bl$U-aU&x>pRu3EHj`rNHY ziZ5(olYG9-Vfnu8O=mkdoxRPonSZ18Mo<2v4c)1syRR!{>=N~e-x2(cZRf-}hI{UH zZP_dHpm~2(+^u~xOx}X({~deMQ&(}G=oZ?#{#}34^lq-yAKWiDjz|^=3y=(-|6-}ZuU`W5MV!|BD7;^N;6(cI zpk7TU1=dGD_4G*eM>+>Bh_sBlY%fG02W5)4BmgAZ? zcP#L6$)EA{&*lFU-lYZ9h21ij=G&?6yN%UBW66g1$7Faq7B}ABAZ3|;yZ z@?WggzB4z^nvgb8-@Ze0a>Uy$E(sw#KiCcBd{^9G_VN6#f0ORNansw;88TZu{f&A7 zQ{e}X-6wtY@7?~{V36$kJT%*W!JNy@$A2C)%Mh>Z;xBpub_@JMYSbz@HnNo`l6)MHpHw@8@imH_m&;+jfv+tJSjHy$%nT zu>NT~DDJ7Lzy7PAmyuH0=dO8z(`IPzt5BGJQ?}yEN$&$5>+~-DWjv7On6~u$v^W`m zEz8Q-`O~I9RsQ(NH(QeR>-D;X=Sdg);_q#HeTcI!sA}s=^9%;Jn-}&d?&-L1_{tzT ztY7Kc&%{|-^H|vTF5A1G@!*ZMTk_0AIXHu5i~MFSJ*WA9dv%*2|M7<abF**SCGi+g{1Vhe>#9*UK(G}AjL*yXvJ+5Va9?&d~?#}6i!MaO&-_Iw%n z_hYEtjGQJm#VdA+F2}mht*SF$dH!0xqeb3fwb+`n&do*1_n1qG(PB@36K;_9(3T=<~#?6<9U#^10a5Ez5_#IK7zk6-+^H% zln>Gi!y7ihSPTqin;9518W74{x*WSp$@B>Ng$_@sGf+hxr0?>VYP&>MIF)+LUo%9Li zBU7XM5Ew@HAq?O?gcOE=qWpr?qLRwsjMUT;M+OEK250cz1QhXr#Pn201_oz;U*`}X zVkluiz{A98;jKm^_ASR#u3!B7se!#6*rGzaQ0M+S!bAh9sWS~k}TM}`^(m(-lpl2nkV z92xpRvH?YzWr-!J0Y&+sg=(I8C5{YJFvWvQiZb)k9T^yoG6kg;m*%F1WE7<)rZ6z5 zfWj`IC^Nq(v!v2FC$YGgfx!x--?_A?C^fGHO6kX{G}W&%S3G*qGb z92po&K{_G+0y*0+F&C6xb}$8{=H{0n_PIDRFhqk?g46_q)&!me@j_B_3qT@JU8iAU z0f{9UjtmUmu0ejT@Kkq?5gN8m`4x@~Vj#6{`N^fl3=Gyx!KryEP+3O?h7$~~c_{&j znRz7)46z_}!Ii}&sky$XB}JLZ#S9F*3{I)(nR!qZeGD$CY2o1IiXd+}GB7YQ1>_ea z#bhv3aB7K5PP%7FYHmnsMTsNBHKy|7f}+g4k~BvKSC9<>iJ;(QFax^;sub=ZEvBH< zoYchPR2OFk9*}AmXNK2|zWHUT5NjD2Ng8?VIhM{W^n;%NhaJ# zhE+@t4of|`WE}l-A zCAoLEC%oL}h(&7vT26q=vs54G72BoHh3l8tpN`^-uo_|4Vo_A^`$Omsg z{J_%GqROzuoYGW~5W_W)m}_2XE>z8H5Feq&k%8eSNF+G51fl0FNCZ+)LUmmLi9j;0 zcWR|0!zBbCtj&?Z(Iw0=0Om{vHpbwL)SMjGiqz!Nl2k{AU=LRxSR&70aLy0S%goCx z$xO@vWfKMg2IqW;b)I>U70V0^Rt(Pho=9r)b5fyUz{*@)Qk0xg#K7>BDK|AYxuBAP z;W>x}UXaWSlFds_EMZ^)u|Nz49uO-h6|@lm!~*MJ1G91q7#IXWEUWKfmBz%Z97 zEiDHe^J_tRz_QMa@tOHa3=CVC(#nf6OHvsaxIy-(m4GS?hTBXzpi&ylkz)o^3=GWB z?8_j|7@t;L0$P$S2GUcKpTod#kTJd>8N4jol`%fE7{X+zWQdQ?O^Q!0EsD=gtYBdH z#8g8z3BxZ22<^=fACX)DszFOqix_-C?#N9_$^n<^XBpy?!HOBC zGePLJ4DpG{xhXk$42Kxv6LX8xA%-yIF~rBGro!Ata(-S(QGQNZ z0RzK&P>B~@l2}wyTHu+NmhZ^G#1vm#Qk0ho3ddiJAa-(YK_&ykA22(mw17c?DZV5% zw;(Sumx19rLp(HvoMDUySAIJgkap!AzV4h#SD;XHT{NjStd5 zg2a*x28apq#TofUCCQ~F49>xxE+ctj5Qd5e zhd2g>ga$C6sY3`u)w#F^dx!W3xOfJ+I*0fNMWU(4l!oea33~_{;4>s7>)h`q( z4|Nwpn87DADW*8HBvrQ{F*!RiJ+(N7fdOg+LvV6YWL6^qr z6;v`985k&ly3^p@X;2C15Dh}kzbv%~+)9Qh2rA73HHj5q>OjLP#R@_BxruoUAsLy) z3gMYmiA5<2<(WA-3YnlTNKTG|YOz9oo4pbY`@=Nnl6f*O`4lDxqcZ(`Pj)R&D-E|1^eNk#*X=YJsijG2gMrLw` zLV02_IAh>60_HZ=Vuj3N1(O~ZbF!WSq(_YEu#$X*(&AJrMEMGFPz;1} z&M!)hQ7w)E_YZS&Kq;4j0cIaOEQ%F0;Esd&4Rr7byv&9ehGZrKgL8gvL1s>7Ub+IP z6fOoupk6^`zMh^Q0|RI@0VD&71F#Sd^~qpKL>W+;SCv_S>Orva3=B{ofQ$u;fHVe` z=H-C|3W`#7p%Dm450E^}5R{sjQVDVyR1%byP*Y+OXsibur%)vj{rPDMs>ON=o@tK_3ZQMc`Cv6g3aE}QPAw_P%u6rUQAh@-e5b_ZY*;S9 zlH$w3`3RJp7y>He{R>Jmb2F<_-EtDs83HPUD~sdZQ(*%k0hK|edGW!Sxdl0?kfA+L ze8#(F=0OGp0xIJ{MO0CKdQoC7XzUCe-|@&p0t~9fs>z_R$jmLsFDg+esEh~Y)Xbbz zYb3TFR7^pm2vib-(}TZTNVsE=D>NZMDM%UvEh>r+0FB8p_yWgvk&epH!NeQ<9k%AI}g_>64$A9`9C^pBt8+nG#S^ z1PWh}e#jUuDBJ=n9gEWA0}_jhQ$tD%Ks6mmO}tZSW=;y&T(F(aZqRBm-Y>PBA)pd6 zk_C!N$C8pFMBG8egHuaT#XZ5X6Az&vqo@oF4Dc2e)Bq*b;uy$ys%o)bYDKD&f^Mk- zNKmy{w>&csl&tk~^3#<-?k-PaU_evOU|?dP5R{s(;NqkJcDj{9v}$pTYH_S;u`Q_f zaLzByNdcE%#i=C<*{PKZWuP%K@Tg->VkJ0*L5c@RnW~^#%wS*yH`6<{5^Q2Ds#(yw z9@0!>P%YLisDzftmX?+ZLHYS53SgZeT?`Dt`K3k4sgOEJOG`@uS{6X^YB6YBAT>on zHBw(SS6?+nLDj=b)mK4FOADM{z@9-42~b-K?kCV70LW7e43YVz3Mu(ekEZ5=N{x)f zvQ&j)#E$XANl?qaTdP+gE&tdN_SoROIaIx7HH1VBn{P_r1+NM>MQ@bGqx zjCT$V3Uc)ei3bgi!o+<1ogIDReI1=WJpEi57(gS-pa@IM$$_Qfw9LHB;*8W3JuXPW z15v)YdFD`+)3K|li@*XALA#F2onF%XHAl86Xg6bz&x&=GA0AZjm z#GlZ%5u|(tWj^pQ95_r+?E!~2&IIOJ99o=ONIcyRY=WA z1x?nZC=?XsgOia4@<=cAv@MV=DVhvLrFn^<^3o|W1ys2g#Y0jaY)A~60~r|d^9w-A z81nM-K*vxpB$k%sgTgf}zbF|bP+FXdu9pECs_~FOU|`5iElC8OfdJB%mzWFE#!!-2 z1nNpb+cXR?em=Bbg7Kjh1Oo%8^h}A*%uCA$ouL6D85l}3OTYyqq%j9B{uvNy5z@*? zEmD9U1(6J%egdln6=JY(g!C%G(+VK>Wu}3WdT|M;d{a_L&o9YW2m+s(!NtIkl9~e+ z24#9HxF`cdIRmK7(@O%4;v(!%0nepDaz1iefr>+lIEGSi@tK&D15Gj@H|Ro#rQzkU z0wg=8rYJI~ra&7*iFqjss-PnxK!=ay7iFe`nsuN#uM}d`aKX(1AMSxLBN<+9C8a7r z+yUwaQ>E;J`xUM9QcXe0(%^^$iJ?>(s>L9GL8}pH!3_%c6iAl?rCWg{4mv0URH}o* zRW${r+X6NZsTyE#EJ_78#)?a!3`DC86tG;#EoFtYqWoNtYD5>r9%Ki!yh+Z_OUo?E zWne&Qk1#MWAi@`1X5{3jgPNKQ44?(8MtTZ~Md_dhI;gv#TM9A{M1~jT=cOy;mF6a; z7J(W!;GuL_`UPcnP&-TkcB%>}LKAZn(^J9m2JS-@K$>b0lZ!wTCY1`{A_H_fOC>ae z6f;2Tc;vJSc4J~uazD+2?A1p@=xYJElq(0ESkU>*ZlR-j4 zf?C>mtLuOLUKsQazSgXsFD0wMw+l7T_(n1b5>fA9W-#MQtg1Bm|)uJ4zF*Z=?je+L>mIvPd=8X6jc zNJqygB=IP)tbiA)eFCD(mhJzhAi8X+fT(~7$U*^;)Y7(P0%HIF|No`{W`kwLQ1tzB z5Oj0`oBqqe-NDVl&B5J4(9zK;0_+ZuEQI~j0o^_Z1_n`(>E8rIp%9eqL>}}``-Wm|L6bzf1qpuN^kE#JQxOFDhnq5{|BWXoIq?7grJx+}A8HrWCXlUSOW|n~ zMIR`Sfl@}GpFQQ9uw}mZ7H?Onu+L zIUbVNML}!^RRM6CL`k2R`XD(TlIEj=9335YDUNw z*kFcKh7yKS2GFc1=prBnMqh?t1_g#-hD3%uhGH-)m7$0slOYXc00IZpGH@|`XAog{ z!OXz$|9=xh2tx)#CRj%}LncENLn1>FLkfceLpehxLk>d@g91Y)*u)ZsL?~YstU8|| zk3oTCA-c%>b)K_+Nnm+1=o&Yq)P& zI5q^-GRQN0XJ}^l3-)g#Tn^L`;bUS5U}NB5$Y5B^z{mm#kN^Kct#5=f1_q{pT8708 z-$55HLDhg{(c=dk3k=kXA0Bx8fP9+6kjjt@_J09GB|`~A1_LDYQDY_%9GVarNVt_U z6f>kUSb>}i#|<2f0s9#`7`8I-F}z0)6R;|fe>)k>88{d~^#v%61fbaJ!jQ>O1a%Q8 z&MMJtU|?WoTo_QxAi?mRA&cQ3*p6nf$zXzsfq|HChq!Yt*qtB1W+25K#Grs$hItH5 zNbdARNvX);LDcsjtBmy#G#8PdUS1?4VC4#AZ+K>5;>Aq}2)K{*{%W*~AVC~nIbG8s}C%Hg>( znIQw5FVn%f52U^lEQc@$R0iO(QvsX~OTg(fk0G6*m_Y|z0w78UCx%3DIRSDVvQLRC z4RDtLpjc`MsAZVT@Qs0w;WaY@LoP!xI4|We6f?v#6fhJq^1JvLws4+R+`;H`RxuU=^;%#$ zC^I<&WH!_~h5%Ov1|LQi2JZ{Xt}w9&49tw&j2sLM2@DJj4G>ZDOa=x90R{$!0ubK; z!WMvp6G#LUULcx*fe|bxUBJk|1Qrkg^T7m2Khzko6axdpoQ(_&3qYI)3=A)XK@9Tn zg$)c0C~;K4z_0`20IUY;m84dbFfcGIiiJ7|Xz{7!ve~QW8rN85nf@p?(j5 z(lA8~V0#_(Aj%kcWEdD24nWNW(WvSU=oJ+gB{M+%`hbCfL4kpR;R2E(sClR&qrA}& z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu;sYKK7UffB(kj+T=p+zmhVOPP-`#5k0` zKOELwqQcYqjlZRtfq@~kJ4Zz%yt_t4Ah^Mfp`;)yChWgxM4=sd^Se_uFiaQObVYm4lIifgvmd)O-J5$`KY8{J)gv|Mlh{|2tj(@VB;u?RZh(z|j1I zxm4z9E4r856ZCH{*(0fk`gACOQ$K=^;r2L%ocmZc)4oZ0$!v9Gca_!@^reM=ynxx=yW~P?JALFk&#jCz|iS>1ElDq@wb4^*eh-? zdW;wtx@%>ckI1whDB)@SU!oO|5m4;F@LC$Aj3wZOlnDca^|8pv65-bWCA+68zsGV!-^FflMR+6QQZuoETqBxL&A>e=M7BKI>s0m2<5rNhN-L7-^TlRqx zzUz{1-zB{)Cz>r7I$bxEp5Wi_E(Ov7F+T;Yfv4AX$BUNV|NnyoU&w>(@Ah2)c9;AA z(iQyM-FO1{x4ZEL{4bN}b=~p5bV@)L!;62vK{*wal$1b<_WN`EFO&FRCKC9+v?Jh! z+h35ZMC$>FJDO`(FqBAiyRLBv=ysjJzu$o?;D70qEVlolE1Hi8K!yIhuCa7o!{3wr z@Bjao-v9pp2RU8l#DC)#-2n^?|BG2VT{nQ8SQY?^k9etW*A3miTbNI1-v9}B`fhoV z6aWoY0kHFxLEFoGpYz%QH4=-GC2vl;Ha0L7pZ76hLi2Pro!t!6#1EgKm<(2~j zL*!zR5}3kL)*W9;92gFVWpVr$Z7FnM_+O&J)BORWBeIii;hIAZ43V8~ES;txR@~vx z-Zd&E;4I05oFzLz<*)6MLkGb~CS7y|x_G88&6EC!ho-s{>Bu-~NAfgwEXzo^P72L^CCUlQC5 zv7%HIi(#)#jlcabZTN5Ub`DF$5r#>e`!O80@w*Lw?OhBCxA+; z1M%IxAmU`3=9k>4|kh@ei{Za0$Z4$yO87zi@c*J0 zZh`#p`T)pa9Rw^K4PMHJ4f26Q#KEm@dm=RPibOiLeP6_CBoe|LMIw7Ff zwI=}N98iE|Oek{zRo;;BQfWO<%5Hq%Wg1x5f`E(}8ypz27{fAFYykOw1;i?jfQ+0n z2Zq;^yImJ_bM%JJ3Ch^90Tda6*2ikO12SC792i~}|NsBL+jYVJ(uQu1fd7F35GfUq zR0K?_1uP{4kzxQz+5Z3k-?DT6z~x&NgdU?o$)N?agH zj)0W>`Uf&%2GocdV0jgY`~r~tt$+XjXRv@hxFjI!|NqjSu(1E4KR{Kn>zrjDJlzvzY{2Zop1LB$Km zynz3KAO#SW5Xp=eC7{p*Cxz}_P!z|5(vgr4C>?EJ1f?TL)2G+#lj zc1w9+Nn51#Kq=G7!~aYF{4f3Szx2z0QHLT>9EjS0sEj=z)zH*-^6-CCh64@^8Ap%= zLG^y)n;fLX=K2TJ(sC(sV90oI%YmV{b;jTS|1*A+IWPnqZ=Le@|9?=R{l5+o5BM+I z0csJNf^ur}5rN?U*IEyhX8t!#0BLny^WW6tmIFh^36O431uxJGHX@+6b;ZB`{{wnm z*92rRGB7m!7gYdfb#Rrc1(AhTR3E$;7$5?{0WThVf$Dkz2tVNewQk=(;6S_U1rIKX z;IM8}P;n66{Uc*eIVf3Q%V+`7-8cSU%g6z-I$gi~zm^dLVgC7lEyD-G{PF)gbDH|)c5c(0S6Q`9G)P;;l=-J#wRU( z-|)9^GBPk&`u^Z=0nLJ0`hMVV`OW|m`NH4wj)8%}`X+zNGqAu5{+7ESfrI=lSHS{L z_*+12uI^F}>yP{`yBHW4Ecp0aHZd?T1cv<=bpfR%P+uZ4vQ#lF;{+%uMW=u&paTv8 z|3xQ&SVwrWm?K_xF)%P>Y$;Dh^ z(d)V;Ao#fJ5730?f6*SWDFUqrO7*&HPxQL8zF6r!92gn?hkl3z1zPBr;QyjFAUC@Hh| z@*ra42Rt@xK(PT1VQ371(jPQlKrvtdk_Xv#t^0%V0qYwj)|Re+O3W->zmynSy8bB9 zvHnn^k-=~R($N$hD-9K9YM@E(~{lE4<^h@{g*UZf){)1chpbk`M zSa=o#s4g-oa$x8-1qEyOi_RPsng7?iZMT5@eZV1L_kjurhTzcu*Z*H@KA;c)Vl^L8 z*sXBVf#Jo)-~a!EY$z4Vm{Q@u@Z$9M|Nom${D1is)B-F~QFy`r|NsBU@J<&Mng60+ z3LF?f25*2G3^K7>cMXhp6=FU}6l4#mgDB8kqawk;-vVj@WTb#B`0d8PaGXUYp#t2t zkm&yLUvvd1^i7u(Ixu80b>GNh2>yR9_`m1^5T7B7A-K~;#pM6BZWk4kUKb0-07$!} z`$O{)jn;1^=3rC9UaWFsUSr5phn41x>{|3%*vI533$-wG1%ZT<86|NnshqI*E$bA$&X z`d{<|sQ1U#0hWcU0kzh(KZN(Tu!1y&1^gG?0nwEuka45}RAPg~1OA&jfb#^XA^@jM z;{$Oowz@(SXX6_d6z7Kbx(WpRFXe&Qk^ypdz<<#bAdk9oG#?T0;BNuV;(B=Sw@hGQ zVDKm@@Fe z_1x=ZsQo2EpkDBs3UCbwD=qXuLjs~lU`;ynj29sJ7tdV4eay&GHPC>7`PcvdBVS9S zm~#cB4qobj!tj9cS%>%+1uoD)3=9klH~t147GMs`V$1+7B=}#-@m~})QqldR`Tze? zP$$tZ7*v0U{}-JDigQr+JS@EX21;kX)Ai5)>&>-)AR1&qeRvj7tq$(R|1bU1{Q=aC z=joK_J{BDxck*zvt-&b=28I%+X4?<99T*r&EyMnosBrujodU|;9~OdSy92ng77VV3jq9E65yRmfpc4)hCbpPvQ)UIOzx6U9r zxb;9}r)vv;%VY*nGqxuHDis51Gvxg*ZRvIG$zsTGs07D+1VlW{_&`8F!2dFqfd8c} zSxhfBzXf#`Wm*q(x^|R!|1V<+hvvBdr5!K&ofsH;YuFjXA^n$b--hNRBL7P}IvKlN zTRNE`V++Oy9A1n3ha}GZ3LvjBfxKpXpt~0oSm35ekQ1o=21>=yK^2*>Zqtq;2ZnIt z|F1=QU7rN}7wsu>V1TeDfJR~%!^6YDTu|fbE+}@v4Zi?HwccFI!{6=7(doLOMD%~D z2&85U_%B)kYGi_H&)~4|Uf(_7?j9)qZ)7oc#_sqp+5j@1D{|)dpBWXE4h+U810f)b zF=G!C14CdSNQVcg(QE5c;J^?N{Qr6uV{oVImj9wA1r7}R6plGCguN(sWMGI42+QKn z;t%eY4FHwwmoxtV|IhHhbjN>D3s9PR;SW{AmBj@Tz5Ks)3y8iR@L$xX0OZ>}0sl)k z{1R7cD3P751VfAdy~>k3hpzpdlh<2T*d^zz;1_n%@Y7h4;FC2mqHNF(BopAS<#M z{$I;t?EdlpT6gH5?i1avU$if1yZ*^y2q09 zo00$ya&T(g!2$~2|MrOBb^X&^%i|Z`UCY7W(hJJEwLAi#aj4eC3=9n6FQ)$f|9>H< ztN;ziiGYglB1Q&C86N&$GyvqQ@c*JApw7k%P_OMci^_uI;8Obi|Nj{m5CPV03Tm7+ z9|6@c@BjY~$aulbz!3ibT86_Z2ZoHlP)-A=w*abq!v9~3d#&8fa%I3=F}+ z!NI+)1^>a_7ny+I|64(Z9fyd3>n<5^@x$LL&&a^=nynjb(EnDDLDruU>PjRmLUKuL!iRZ6qLe1GQF;6GBQAJ`hN|~ZLRtL|9^%JM5OtML~kpo*bV@D zB)AtW8}NTC$oU{AmsVte23jCRX!i%}&ymMl6aN4I&&tTaP!iA!W(R}?2M7P(3Nq%u zC=1x(rKY{EX99vV8W4v~f#!cgFV9!CGS zf;$g%)a4iWjp!FO7lmnolEKol;Y#*p+X??83!4{;YRPe?6|NsBD9w;^F z1owmE4rd8uT&Z$k$XHVaZeWW12ltv^bo~GS|N9N;1K)2jA6pDEDeiD{jS34>9or6* z0}c#_OL>}WR5+NxoLbf$EFiHR0)h+-FP7QBGS-pK78M>r28Qky6$wEGhUOQ3!J%Q@ zTT~PT85qL(TkbJ2FzABgS{IauO0+%rTltv5%5?-8820~TW?)FOKF;6zlZk=h;0sXm z3)ISf`3lrghh)bRaAOLd8^iuz%>a25tQ9o$#{L$iO_|H}c8tPgf~2h??-DIXUVky5`bMhu1BvLLtY2aUIcXS`=-Uc+p@93Imab8W!=G62V><6$$8YOd`aHa+Y2f6`6n+ zL0~ROJ^&h^VBfxW>26Um5M*G072hVt|HH#GK#`RJO50(Ox}*8W|5BDL#@9?)%;Dg& zJq%ixKq9!iM@2@EfdSOZGCpH`;Kg?f28PBXpqvJ6F+_leN;N>40ye?31k{2BMFKd} z2Zwdv4DXDca@=*sf?5ZL687V+GkR(r7>xgi_xkn(hG(&ahllU;IOxCt>PHC_I51cr zFEMTY!CESlB@murPzxHNLmE*tKJc=W36xMIYQgn6l2o_tjY0>8eW2PeEVvikY|db* z1!a)yP#&mj9RTUVE(Z0NKm)J;MJqskEZLGmP#dWs;QwW?=;eTnD>a~M6x90802SB) z|3$BWRf4)T5Ca4Ln}Uq_e;GU)ka400)M$Z-G#~N!UpgaW3z85d)_Yy&KoU(@#)=xS z2s^tysZh9?9V9|*``V_;$k2ngtPodX*5{4WX$lhy+z5?Krx6F@Eki6i&D{+IUr z7X_8R&9xnjrIH|h8KCgYs6ev2`G`OUs3{3`12}LX&dA7sxB}{<7aZoGbSL2u02Kh| z<`|Gsr4#;(ihwJq66q|43>T1Nzy<}NjCn%!Wq|Y`9HRm<0UBCi|4XMpRAq=DTb>~R z=7EP80#0Wa*$ zKps&*@yNa_)u2>Enoq*8`@;A@^AQP$?p{!Oht!!jO+hWm6_A1B-Zd(qIvuqg)C%gP z^tv{H(mFWVnZlFZHE^=K7Lc)}7L*V>kkcF}c|emIcnA*MNp3!((t02cJXr(^j7Ufi z2sHi$b_TTn)4N6`;r;*rJ3!Umiw;v*?Rd1ab&dc7L-XD>0t^g(Vd33-R|qgLbka-Kh=Rqz&CnKQI3Lt33u`Zqzw2 zK-!j_r47ejTh7#hx&|GP>ZrGM20sI2Tsz>!ViVB7GFae19cW6l4IuzBG2lh133Ozm z`G|z|u@dRtUXbcQa9NtM0;C5vIv@}Xnms@ataZ9h0S$hkj;j4HodOyI2MyVa1O#Nv zs6#7UAVXroP-Q%=2TDQ8I?$D6Fx4}F$MHaYn}GkK9z~#J+5_<$c%-h=wc&-z3sAY< z`oF{q6tfV?ET(|&^Dj=m1a*UXKxMCJKt=?}_-oC`NXlF&Q>kRmyTX^?}|Cd4j1rMn; zA29&UO+iL*93O+a9-txPa7c;>3j>dJg+nSFQ2$<|yLF2IqT&( z9iToxC=G-A{BcI0thYb_(gW#sQDOOS(Cwna^WWe_y%DI&11s6!DJ z4j#|wb-fW_d^`BRXbrf(d?TQ@^$uv1+o2v*PK$zS7pRC0L}Uf1J}?E<2N?z+kxthu zpnk%Qfc+}nP!DU&NOI%p5YeR6ri)uqqxh2r+ zdL#gpaKN>GFsPFNu{$6boUK5!RSapJu^ax2RzS*R=Wf%I0tbeykN-Hr z3I|W(fL5I_9|H{{{ee_Wc&td=Zvq1sXp@ zUI|vp6n7Z3B&pDW!Hs_(TL7r<8Cm)TG=yFN8tgetlAacbo*fdPk%~}#Sd{_lP>BeD zdq^?@pp^pPG!UK@9q|8}@qg>%k&&gK9(ofa1A{|WY(S@ripc+K-5;#KM@E)BKgP!3 z7M8K#paVl^iHg9B*Lu+Ic1r*#|AmH!A9qnv>8W>M=&VuE=qyoD_%Dhm%RzHB-MX-% zd_Sla7ak0nupm&7LlogG#{ZY7h(K02fD&to8MqCd#RTdGbf15r@&sH++kys^OR*Ki z&_eqEwQgN_d3+UAI>XCj=oHR>_*4tHJPwAI#vj5UrLl{O3%ocE>wfY7dN5>uqxp!z zF&7;bhK3py6^7X863HxvjEZ^(Q1?IuwZ#P*b_R#-i$GmaSqJLJ_o#p(DL&5l%nK)7 zP@^3bo8Uq)0yM~{0gBs7U0B>6?QC5F9^UKTx`m&C!T5IbUeJITb9fe4XK71k?1awP zDV?z$ov{nzOT>bMGgdS@Fo0&_iX0e>4>-IGVrF2-Skmag@V|`Z1-~vNZc8|WgEJ;H zIxxJxn$ZK6@Q znHU)Um(J*QT>=iTfd8T|K-C{;?I3^4M9^rB>>Y61eThe>>zrQKB>^v7bQl=AKY$iT z?PDtuk}D==?9RyV=ksk7@(ugp%ebQPOz+<5ZUe9)5$WiMz;Al zSNHLQ510Z1|BF5;1kJ60T;0Rp0;&>lxTmzSyR^mlHoSp)oJA$Z7`(KubON}-d;oU2 zK$c2INTUNoh5#ZMn}4uD)}mR)PB_jA>OO%QR^T3UXY36AzA8orhHg_(2Q-T*y4!a~ zw`)%?OUHkMPS=L;7jv{BLC)V|4^rmZ&}{+oAGqE6rvX$GU28rfV12mPbxEm52B`ZL z@FD}Mr&J7L9cZZLC1^G|i{WMMzyJSVFuwzh)$q5z0j)w52*_B_0BglrXq_nB$q7g*ORm9% zeUOApWx`*i|NQ^|H7GfN%F@Fn?#(}#OPv0@E_iJV*J6RJg(LjMFOU{lxU>qgG<*09 z&flOJM%NkLt~0D%C-C=70WBT@$7mVHf6)d=J!l(-7Tt904zCAOHU! zX&Jkq*L6vW)c?{2|4TdmmrlvJ0BRJ#h6F@ji$aB0APWm*Ib=)$Woy?Z0njYM}fq*dZRN(H{%%JI5$PE7VurTm+U@&w>|9WR> zM>lx+7-&^8D4M!kK?`$1!*k6Kc$yC+ce*zGH~SA-0XZS#L?gJR1R9S5EpYlD+R_YK zrdY!K-?is|X+y8;g#Tt42T&DdF~4~G2vpWeWHJ0N?Lf8tzZs-43UM5GWTU%`rS)5B zPj@fa!>&D$0E*QB1rTV43p`}m{6+va^`8M-g+XZEpQFivAspPG$@tL-W_|!QSuDZ6 z2hSpUS>G#hwk-WqVrNF;2H|hn z$p9TGEs4?pw>8aKoxa*18P_XXh5geMTIBhLK8f7L3$;iF;5p24#KaOdzzr@ zQ9}P;dwB~~7M5`|{xkgl|9^=_<3G?e27l{ef3ZT9fg$u5ivh!n8!!I2(gI|PP0wqpRQ;ItyrOAQeMd?*!GbVu-hn9dk_~5Q>iDiZd zvWX@j4yZo|EikUV(7p-n`0{5lyeI$-a6_9)Pkb!B`gBx(8 z7+>v|+O;DwqB z1B3B_&e#vVwO>|e8LW;ATixqAAvmBjw&w-tNQCAi68jlA7#M;(OAmC`9{Dc{n&j*@ z)c{Xdp6GQw5YQQW=eX;QFRBg<$6e24gQl6Tb-I24%hVn@?s_JNfq~(;>x}?S2ZsOG zI$ggYWO5-grFIaR9|)N|h)kLRNG7<`_0MtF9utt!VCy?U6%j|L>y%#C51p=SK&wTM zyG{UYOKor1e!m5aRRO=D^Eo8B0(E zL5U-aD+4sy5u7mvEOi~CJ}UrR8U$zbG&?Y4G&DOf{D;bbJMbBxQKR6D0*DsyfaeR9 zhyVWvEC$Uazj*Q&l;L2biJ+|t3qb+WdH^)OUBV7pbzR`V03OHyO*aNWCeFKiR6t<` z8UI#wi;_>fInW>;w&Uc3*h$`7Wp^h>VObVSix=T6L3bX$z7sm0kD+G}yq<{DZMh zu$%V-XtlRtzzgj!pj9)r;GIFB1va2=Sc!r|iEI{IhD8gwxO@X{bAoz79O3(US{xW& zvxV<}(d@wR;`l9a5gZvGcSwMt+ed}P_>%Rh65nn|8S6tOZk+)#oq-~qfgGKI62~14 zKqX&qhXE@CL#IGz083{8&v6G4h6V(6W7 zZXXo^>+_v%GPUfT6&&Ahz837hc<>jCQ}a)Q&H#>D=H`d|y@5ZPf3cVIhjn_WbXJ(W zUz?%)&A;=pj+A%qi@RUHB%dO*AgRt5$J z(3-Qr;IQs;ndUkZhVC1sirs!H#s?fOKLB;-$~FFngPhiSpwrEyl+EGgA_hH4RPxij`pw>!`O(m&dV1G?QznB5ElUO35v_6GfFKEk7YqeQmbohMlPZ~({* zO^6wtaR%LP8l7%BuVoqC47&Xcy2CWAPnEKO%g@0rl5z|Too*6YOueoz0*<-9kU!@7vipCT#0v&FP>4zx z-|lpi=#2evoDGzwU!DW4ZgEp+P7`2gJy62Z>|yZ#n;Qose@iC=0|WoI!v`O5be}%> zl7snRmb~$$$nPIpFO?Q{A6DVF;V1zu2|Df|v82y|;bj9q1H-}BEZwJ@AM!UJ=U~3^ z-}O)Pe}T@}4@Eb-Lpd6Lb9BZDl<*vPg1Gex9|J?$_rnYiz(O$~QxEbnFm$^qv>qto z0(-alA!8>;+IKer2Bs2)j@TFA;5_)6BcLPpB`7dA%P=s2lF1RCPB#hT+s6Novw=>* zY5uQJtPXM5VUUTgSRJNNbf-I%17aO>X9Nq>9)s?~2VZb>xV{9@0v)ceK=!iqy1qF0 zS|Xq$_BF^}YfO7VYmh(z0=3s0x4i;L_KJ0%ZhZJ3w6@NJquWmbWF<$ZBM;Om*t~o| z8pX?=FSKaumRWu+6#-Vuel*%$I_f2 zz;K)mR2+d#VBv3R1Lemshi=y&))zWMzmzd|x_)V{{lZX^-0i2(da}f%+fe|t0PjYL zR=1-_x2J^mq3+Nx%onULma%A`c+K4H`z5g3_s0LyKg=frU%Ze4Wf@qYh=BGtl>Pxl z!xo4{r<+8#>laPeAB?VlG~FDU>l_&PTTk&o!YR#x;W!%`11PDv2{3S!)I!q5Y90m# zl)=L2*y;NP?66MP8zs`+fikU^Izum%2zG}#SO+Q;b9Vb)=wK22@8-~YpoF8_ z_lvdfkD?=BzcQ7GBb=iu#Q@6l-L9aa=x*OHn!Z06egCu`;BU2sI0h-iWEh&kE@Owe zOaSIGL3}P#L3Wu2(Jt$j#O1QZ+~8nBa+wOlaW)Q^tC&i>K*wtqq&ZS zp+vm*u#n$lJyQZKVZE0y3>uN`*8CE zX8-0F3?=#=C9)nRLLQ|$$DJfh`anrnnt_4gwd`>=eh@9%eeA{C@BjZFXA=T(*^aaE zGJpzvCjZXZ5B@JZI2jn=E^Gb|3KLk+?FLCm!PhL!@A(?*66CTtRBnnp4~g82-CuG}mPa7d`8312tuP{c<`P zJKcPGr+r{zU<40kWL_&l%q$0)iQEk0-*yJv5^6rqVg0k{7XP+0-EJA6T#?r8dn>Ke#|;`BFS8jL z7}A<+ZwZt}@^3rxS{`DaI=XoE2z`V-(m(@k-FW*Ew9t{ z1}J`+n-4G_e9h$V5AM!@%opU}cBM25X8v(D9AVJw#BBYe=vMcs%dfNkf=pruo5cLu z9OAG(+#1>88h?T`);AyEKKPowIrRoZr1mN1pWS{b-C-HbehHwu4Akx_=mxi$cJ#vX zaJ3Uogv`V z*LtAzO}ASLvs;3tTLymA7fBo#Y_2eH_L?A29Q{K0g^;6(z^XZV9ickNxb|> z%K^xQ&595Ft)Rwrr|XAC6F|yd+e7Tnz_8!<2LHCJ;BvzF0P`t_%b-?p_bCtM!O1`$O1Y1-iGyUf2W(raW>HLlHG0|tp`dht$!4&K-|0qw7L{I3xfg-)S0oa^C-Gwe95W% z#N|g`%9k0#96)*RIGYHlr1y*Hb_)Q93Th0bflNbi}ui{roR z6Nmr4Uplx-KR_DO`k*!J63w+BcNBu$;rpb^&Edc6lmEVNIv61(!AmYi28QN34hH^~ zwIE0Thd0|G^MOcJDJ)bVO%zDm1MD+KClN+B@Yb9MCE5Yqu1~aGA2inqFn|t&C}Dqb zgpYyYHS3FDP}9nn1Kv#Rz6es&?fRzE^+EUHQdw*bK<0y>4y6S1L2EaO63*jJ5)!=* z46j4LeOpimN)qJcIu3@CdXQNqm7p#pwABkbU;x}vz-;xxn@-koG9`}3og^6G(%2i?9L){X)tG9YFx$BP?pLF0KP zyvNx<$F*ktdC3BDPc28c>lJNxmhRXG+VKL6?i`HqB8+h|pcw3Qy;7pndb>meGKvEV z5oioJ@q%OEA_u6=CDG|_(CvDq`3Mha7>0c(D4U8JSz2$Gur=56u$1yP*WO_Oo!WEfH+b*=otLHW{{QdwWZ-vqV6gLWV7Tt#z~Jxc zz;M>nfg!-lf#Hpp14Em)1A~l@1H%#@2L=XT2L?4?2L@wb2ZqbO4h%Ma4h)n092lbg z9T=_$Ixsi|IWRm3c3@BoabU0ybzoQ)>cF5E=D-jb=D@%g?!YiJ+<{?HxC6tNa0dp^ zSw|eL-%1R+LFukUv)fOl^<;@sw}T0&uLmBHyBHbyzf1-)HrJdc13q;k4m741cevX{ zg@b>+iwX~Pk7?v#&@zbskmWw0vAO0q4xp&>VCfDJ2=12f?)8!U-RXO!`;@jXPgr+> zOn7$%k3aMIPTwQFJy!q!{r}(Td!gG;qto|Jx1Ua@?}2VVgHGQU-F_yWzBju4EINJv zbo<$K`to%9`50dU)tY_*PMy9Fy8S{fKkD|2@ahab11YCFLr--3@-+YCFP3d~<>7GV z-zI$dQMWG-W9T0Un=$kcNTS>KjI!^a?$9UQJYAi>AG$+7Kw5*BbN^`i@^t$C;NNzt z^<<~-j}rH8-yh!&3Y2<%JLtgQIsvo*6I3U6`+hk1Qou_&^bhmJZr>-}JQML6Q@Ydr z;46U+@t2@t_24Uk=7;>9pGb_ktPQdr zDNKJbhW@ei3Qq5l!DO-M$|j zy9C|;`~K(-p!O+d-#Z|czCW~mUs(HoDUQ?h{qo-x6sKK6?w%durJoRH zgXHZrL1q6BRKq|nd(-Lr;k7cvUf&-erNWq&Uh4)WmoLq~n96*5o&IJqgU2PieLpxf z=YC-ZIj=(;L^^hecL}gJLfclIjLLi;ZwO<%J z#LL*aJQ>|P_^o5F6q{SuUf^$1{`>#`Yn|f`5}R2~b7-TIAb7g%S=(Y%X9E0v-Zt{51BQ6d5ljwc3teqY#-M$yJ&o>?fcQ8E!x?OqVJHsqG-E6?))4e$=3mV^m4tjy_&y4AI zt?3S}2=0!}3F|H`3D4r_bQLi^5ZPO6^Le%Lf8&#uwFRBAS2{y)lnL^0XYuZJakWYN z&){h7TTt}An=6YyqSKe7)AdW5WsR#%DW^yC55`W{FU23MPk;u{IINGCYIO4*bCqy7 z<|@gU)>$jn?JCj9*v!V#%?GBAgWBK>FBO;>7>xhBcc1U9B1}P*!)wj=#}w*%g}-{^)yS7>Jn-G?JU*&+g)5`x_w2`I&B>J zxARo@`uNJE{bzKyt}Q6K+3lO5eX7LKvqYxZ_rw3=u1_Q;fTl2CL`-mCcx?qbJs_8f zfng^T69dEk8T}3n+CMvMpX|Q@WxY7=`T?{k=(Rw%t4OEoi%!=k$6Y^w+qb?P-L5>% zzHk0_+jWKtfODJi?dIAH#?IJ3ydWVbI1|8_zC?We4LKh$y^bCqC7>kO58tyw1rmf`FUeZhRPOGL0E zRH8fdNm@6LU|OfI)N9VP=2R($W3Cbj#+SNJX!{B{fy&t*oxUQVV%k^W@@wW;kzStp znxP`yw+=pL(mvf>E5gtnE72)g`r7zXug8C|hCi>FF1~8LSo*8=QVC~o)PLoh%(q@M z8Q*SpWnt>|0i_d8{_UJ;ttUHU|CCBvgN9v>gIb`jEd1LUx_z(kZ)b4?HGe`mtPk25NCG8VV%m*(&vUdGa ztkLPp14@&xnY&$o@NbvUzTA4LPT29kE02Rir|XYyS03g|)~;WQ9)i~PUAX*+F_gj5 zHKQ~1M=5V(EdwZQ`J0!59VOBo`=|Rtr|X+;wu6tDI(V46*}8l=I((QIyV<(WYo9py zlPRs!hl#1%^})-UfB*l3;{X2~F4P8DG2w(D?}?DiGt zzI^#rx35Ge&vaNAf&=g}H~_hOz5XkoWBb{%8Gzzv(6;1B3RDm!P_(+n1;HB!9~}P(%2)0Dn^>BLhR@?+v01 z4E#-?!{)jV?*?&q9V^i3%F*~5#NQFk2%6*(Wd6jzEs&A(#%kr1xm9* zX*MX$4y8GuG$)kig3{bjng>erLTNrI%@3lP|1}>J@ZfJP2Q|o!8DH85^5pk#-N*NX z7~P+YFMa?1-Ax>W>67MK4uve1?oaXjP4hsdO(@5~SFD|(e~j6fzjt3a_)36(+a2YL-6B(GOmSc^ z7MVL^h66+Q4dsIeUrT^^6F`EkGv+uj7>hA~?>= z(g3m8m}M$h!%(vU;REH1MEapQmqP)hO#~d`#v(+76i7KFoZz6K?^NQnLZzW*~HAia3-+VM}HM1QTV4 ziGoIP{;M7+bYM7x0zmp%v`OL4brJJFXMdh$AXxT|C z!|RtBirEefFSdb>i}`WXf#Eod%8^5$1zcZ_Ixs8-F*CLta$v~V0U|blWJLdf*L;Yy z9$0+fkOM<4M;6D6OUw)mk%zM)GAuSaFud5s3Z7MneFCbiU1tRR7d=w|Y8LzfZ3#c_ z`UlhlPFV<=U+FGj+5hLZ1L!C{(6M=tHLw!a$4c3azqxha1MO}Fo&O|y1$0u*E#q%) z3xC`O9fViPl_mco7G&vbeXvmLfl`G|Q;=lFnIh03XhjYTFSJ-etKwP@lyJY)1I-ec zo;l>e09I~&tW-MV07&5ukiy?AAce;hXCb#u7IE?XD+YGiPx{1b|jAg05b9!FA{V|HUT`IWRQa?l}ZH@FhCnzo$F))-~Y2MMo z;K0CFcG~!)WycQ&2Zq;z%{xlKLOVc0FBlvc_@^H1KA6R{807W;q5+`up8o$A4Jia2 zqqvyijswGDmOG#Y;s&5_=4Alw+~oK#ssrZzIqJaB$+1ua#9>(c<+cOEi(iZk3@>hi z#!PMJ90Ua>PsYO>kbV_VxV@MLmYH!7v~7wr>;HdI1(2}ufx|CWFf%Z`G=-ef+4x3- znSo*Pjl&KMoxE2-r04|@DSHM)nw|iWwnq*-Fr@W}egIRvPr#Jy0Wih72TZB%08_eK z4m&W+>1Ta$*ny$jm0|H6FwL^~!C}ybOpe8GK>XGRfByeJ&Z=?Pfq{{MkzpZ7a`Bx* z4h+XxFC21U;ACMqaGdqZA&@L|30`OGjX(eYFKhtqeQCY(=l}ozqI(J)7#1rW1|8}6 zA_tTdK!<`IC~#oNH~~K2@d!9kgA_27a%3^S@BpdF0Ga(CwvFpVfdj)yP?_}u6cZvD z37}m4gb{SG6DS_KO;rj(iwYwvdul#|sXSiOs2h82+1rl!I0* zKKXA7ibfFgL9gqB0PuM@r5rD=Gl7okz4O2HNw+B|y8mB;#B}L{|JRz2$bjP>ni5}t zW=^_Y@4Q&R$iQ$oi{r&qCQzXRI>z(`=&(u9qVexHB0Ua=h40YV=)kZaL|7m1{!q-{ z&DMILgz5i<=3^|@--|xR$AQ!sfYg8pTx!nzSLG;zl&qljYiW&^zELHhvc22UhWTKl z1vu4zFg}^qSRuj9$Uo&^_rb%^QW&&Hu-n6i`2_!VN0Hz#{_TZq#+O>Zh4;E}NR-KS z*Qkhed+=EQEaL2r;9$OK{kw>#`4D6C0S)VGMbE$|HXqL7$Y=nCI%pLqC@6P<@>$Re z(1DwvjBnfR%AtKIAn3&;&=NC{h<3Mw%E1@%-!C*jV1$O)YXwlbz|hTLeA1Scf#Cqe ziQTC$pz_8CUW9?8?>$zTvBwp{^)i6kODebhT(9x?}wC5&>S{HWIX8Xyr;2& zpehk^aslY{f)W*hj18#{3>lz`?*(YvOY;$l!(kZ=49pA}3=E76#^1tTTm-d6kMOh} zDB%qQfft|?1)GnE#2q$10BW{1zYzdCs5$iq11OM!!}t3=bJ%Ttd^hOYvl8Cc!zG;G zFDwS-j@JocphLR;i>?5j5mWldxb(+=(KVpMhCcik-2kPxfNAjgJfb^5>Ri7VyM8eK z09re^&;Oaj?$_L{pG(=hj~#p7R1FzpFI3u)d!R_R7Jp~1elZo zlNw-B2TU3iqTKNX3g}Th8Uj=dffG9!7_RMPV6fZ8z@WE_fk9>$0|VDC28Lfd85mye zWMJ3^+IPXgz`(YZf#J&*h{bt385n|gGB7ypWMDAZ$-q#xlYyaYCj-NboeT^~Q1wOI z7#P~NF)&Qq#=x*@8w10xZ43!>=|7zB1R zFi7oYU{Kl3z+kkSfx%`s1B2IY28NK`3=A>585mM_Gce@rW?(4U&A?E%n}MNYHv_|@ z-3$!#b~7-n*v-JOX*UDIp4|)#r*<ip43}1FLF#Oxiz`(YLfk9vo z1B1jK1_q@)3=BGZ7#N)PFfat{VPHtu!@!WWhk>DJ4+BHP9tMWKJq!%f_AoFk*u%iE zW)B0yu00G47xpkPJlVs*@MRAJ1Iu0p28I2QbTDl@1H+Q-3=A8#GcZW(WMJUh$-wYs z2Lr>49SjUNb}%rU*ulWCYX<|vnjH)bvvx2rbnIYYsM^86khOz>Az}vugVzoQ2D2Ru z3|c!F7^HSEFtF`lVEDA1f#KD528Mgv85quOXJFU^x*h^_1>r6R2GB9FaiD@7Vm*U) z8lrRz&493-i&7IyQe85OQj<&aiz*c~REsr1%6$_H!ZK6K{nOksb5fzA5Vary-^7A~ z%)E3Aafm=>URq9KNvdmJib7FpNoi4Dv4Uy}0|Nt897PZ+=9!mSVuUPVsAr^S%)r19 z!@$6hn_66)n4TJ+lwZM+0&{76GT7xHbJH?&Qjxd}>8U00Mfv$9@fq2vl?)7EesMu+ za%N&qd|G}^N@`JjL1IY;1H^>*;*9*FlH}472IpW;7oT`Hf4`7mC_5m?-#y6DH<$s+ zclP)7_4kWM2t&n#LmY!bLP1wQLd<}wLkL6Fxwr;Z9~4>s7>)h`q(k8B@Un87DADW*8HBvrQ{F*!RiJ+(N7fdOg+LvV6YW{1xT1e;=VL5J1@UHPXQbb5Fv(Q(AEhL_tcVLNF=y{;~*fhB*T#* ztu!wgY%b{P)5NmG%$&reoKyye;*8Xs9AhKBl$;y}H?WloCHV@F7*l}7n1Z@$vATk; zLTXV_evyJfg=z&uaB4|-eo=O2UOGHYYiJ@X0J$Z##4|ZR&nG__>?LgSjz#IExv6<2 z#psg3sUvK7IpB_PjXmjsPAl%^I{dghg+7NsR7r)qcxq!txt z7MFlB7K&4y^C3A79R7L9so3l)E-tPqE-nV$zRKX756VWFi8+~7Sk;0Qfs}&wJO%r^ zg@ii>xyA%kmSp7TK`H0_qSP4G6g}0H7_g-|IUtXK;yNHQ#KYgu!{67{RuvSV{=u%G zKxYU`%qdNUFd5?GlS(slN;31};~4@fU5kq1-SUfa6G7LgG6YmQ7Ny4rBo-B?hLje7 zVwiy;pfcX6G&3g!Yz6~}@9YLm4)K1e$tCgbsU?mjB}Ks{MVWc&Aazjj zV03ZM+=Bd~l6VN^o1an&wkW7HFAr2&LDIdR9w_lT=a=TBD1Z`OenD!ULP36UNhTzj z>q25zLA4mP-Zh{y-Yqi^9Ha~Zl|iL>@xht71v#k*w**%f$AcVFlwJn*Lp&l9z?=x5&nzxUEmA1Y%uC5HS4hrD zEG~wq)l01ag>`UhNoheiSk@UV$-uw>zJWPBvnsJDMWH-1Cr1HfV`5H@f@-lsex5>Q zerb_Ha(-?>X-R64o`Q2uW^%TIUus1OxHQVoE6L0&O;yM*QgBYpOHR!JiKbR$mMDOt z4s4wQ#B5NKV+hVqD=AMbN`+=NC(7n(QpB7bu zDr($jGBCL0gVF`4JcgzV^8T6_;G#e^MKuLn>@uLc0aPx4 z(vUOcqH1VJF);Y$L(NAu9qwLGV8GlA4hV3*02Mn5WeEQjr52WE7Nw@>D3oVpCTA#= zCl-VACthPl4SR4|3`*(- zMsPE|Q!BwH#-f@9&2Nw<4})s4Zb2njHv@yErKLhpetwApSSLsq14D3rX;CsX&uVFD zDL{*RNV+I4Nh~T!O;J#d)K|^bS4~k+^{`U)RnXGX0+%;n&me~cs9l0uozKF)Lc*o$w(|qRVXe^OUq2oOwB7%NK8&nEiP6l%1qBFDdx)0QvkxlvQUY>#z5-hAF9(+_dJs1t;vKoH z*3tsaK!Ds>RGJqL^(&;5w^GOiwG#3Ya}dpo2uL)f6{UhJuHurE{L&Hz1|)uJQ4s@! zv4Md?P-&h5q|QU6ml%iuxbX+8SU~AGASX4kI8~uMF|!1e|8l^W(1UADq^c1b5}+D1 zGY#a`;u284P*OAOr_LB4S(lU#3(b_}|3=D{H1}FKP{B&?rf}x0k zfx$>mA+abORQQA1a=N7;^FU;HQGQ-JtWA-h22uoSwlb&|t0sfgWP(~HB?<+V@u1p0 zGbhy=iLD0}Q_v^^^)Nv7Y(Qnae?duRZe~@gTTWs+LqH{{-RGWK0%?jeIOpdUWafZc znIN|ngNiM^g35eQR$=hTPfP*HK{Q&H{APg77W)>H6I14W(!B8JN{ zQ_De(pX3Zs`#~WwuM*Uj1-F?{4Jb}6Dap)BFV;~=2DhM`5|guGttl)mCwQp|8Z!X* zJ>ww-B4{k2BoWkT1i3vA(u8IxNh|_&`JrtI1{gmd8ecFzG*THDK)sZd_{_Yte31Dd zk^$0AWME(@$t(f)86b_f0!Uk3ArsnS1r-2^xryni;Cv2lJHnNKS}R4li8(oy3b6V! zsS?^4EoOkUCm4|0tKhVpSd^TRS(eJckO11f#1X*2z#+gO!coj1#4gXkz)`@!z+u3^ zz4hIGX&Ib$v>}(7S91#o*91;u+oD&#? z*{vBEI4T$zI4l?#I4>{=vI{aWaAYtraA+_vaBg5=;NW0j;OJmr;P7Bz;QYWKz|O+J zz!Ad0z#+mQ%JG;%m|dHJfun?hfy0D>f%61|AUiJu14jx21BVI&1Lq0`2GFf794!nC z94-tDoG%yz*g@vSFfee)Ffee=U=U#sW?B~OBfhH7eI0}F)(mAF)(mGVGv+vXJFunVqoBqVqoB$!XU!#%)r1= z#lXN}#lXOMg+Yj2n1O*Ki-Cbdi-Cc23j+fOC_K9u7&yEb7&yN$2(YsKGU}>=+n0?=T3lOEWNV=OO44jXk@gK>+z#+-Nz&QyT|CJ039F`0WoR^^SpUJ?$p~=9&xru=Rv{#U$lYxQ5 zlYxQr6Eyxq85lT383Z_(7$Nar%D}*3%D}*R3L5{Z3=ABq3=EvB7#KJ};nm8(z~Rck z!1)Rq|FH}V9I^}yoU@?uU(3M2Vave4c?%l(x(p1Qpn+Uadg^6h;P7Q&;QR%R ze^8ncW)S5#42}O{1_lme1_sV!(D(<1sWJlt=Q0Kc&{QZ#GXn#MGXn$XGidxrGca&S zGca&YgT{X~0|SRO0|VzZX#8h0FmPxyFmP^TVBi3yr)~xY4sQkq&Tr884`*QD5J!st zas~#_!OEQHpz)v1z`&u-z`(hVfq?^*-r5-$INTW+INw3zKc0bsL!Nlqk0 z>=_t1??K}~pMilxpMimM9|HpiC_VKvFmU)YFmV2Z#y_t7e-Ik~u>8LeE&o4+#y>3o zPlU!lEdO7G#y>3oZ$!)gAEEJ&EB~K_#y>3ouSCoLFQM@d%l|W>@ej-YH=*$l%l|vk z^8Zh0{Nu|1N1^c#%l}K!^8Zt4{KNA9RA~Ie^8Zz6{KNA9R-GhQ>cE z|6hj2KP>-mM$7-7q4AF^|DT4&KP>;RM$7-Nq45vP|Ffa-56l0zq45vP|GOC&IFR%I zZ)p7E%Kyiq@ej-Y%hB@xb7=g-^8a*b{KNA9b!hy<^8a?U{Qn&q|G4u1d1(B@^8b3Y z{Qn*r|FHZ&9~%F#{C^)B|FHbOA1(j?XAodzWDsX%WE5r)&co~=&_!t-&KpW~a^OEz6K$ArZIjMQ+B^e6I8L7$Hpmr2Y9Mn34iGsu- zV|}0jJtULRw;xLQ*0~8w0Fm1(#3GPXUcd!NhYiOHzvxbM#@hfn;HFFg?Wz z#i^h{W|%mt9*8VVE-AAF=JFzNe-%701#u(DYN#Yc6R16ypRbS$YS=;@0BQkd7MFm% zQe2svl%G=!_d{_>eo<iz*om`W+Zl`W+ZRhnGq8J1}tcJ23p|b6|MU=fH5I&w=3ph+X2q zpa7j1Q~*x|f=8gi14IgjmX@ZvMg|6^3ciU&*$R#+IjKboptUV546F=n4D1XX44e#H z4BQO74Ezj&48jcJ4AKnp49X1J48{!B49*O}49N_|49yIS84fc%W&m#hVPIeoK!bt| z5Gcd|fx-+BD8c}Nq6`oyz;Ke`2m@$Y4HE-11IP`a{KLy2$RNp}$Y99e$dJg;$Z(K> zk+G4Hk%^I+k%f_sk%N(kQGii`QGwBb(Sb1m6y7lVLAHWy1lb0%31o{HgBXK2gE)f( zg9L*lgCv6#gA{`_gEWH-gA9W#gDiss17t3DngM&cP9opCU4fi4C6DB?_kYPMFkPDb za`DuZc}5+#`IhXsQz`fI>VuA(m!@m`Y~$Qpv$2j{cgORIvim2b2yB=Aq_82&qj2Zv zdye~BYOmaqdi~^XZs67{Uyiq4ZVsD~BYa>*c1qRHq`z-Yrgr(qFpAvFVM|W7<^T1S zm!tQDLY{S;K&HDtNNw;g`& zS@SsOhEZwEeb@86*X~5xUR=G#;HH`t!@aZSJzatVi`JM~hYE{CpXYnJ0-B%o!-uJpb^@eTkk&8=yf4O!!>t&|% zpPP9mZXM}sFYHLTRjkY6@>G`DINpbA)yW#(yEcWa0jnID)CB~%_q8eToK)SK5x%)K zUxD*V+Rph;63_Adycaj)L}gWRpXYlr_dn-k z^>qwf8|Sgtx=iN(aA`3|d&Vb5xqpY)GGoJ&zMoD{Z74L$k$ldUomVD(<=+GC%bihG zw*=03-A&KGzw_tM_xt*s_iY!y+P5L$#o}w%>Ly=Yc;wIxi=a>U{XeGfzL*}qXW3o0 zt@hq#n>=o5^WN~0=34*Fi`gupipBBp`-JV+jZkiOeMVlpSbIkJp*AE$t3_9^+zoW)tCT$6K|RBr^&*@>>_bHpkhaV>aviT8R)GmGD$&CHgG zYU#_rizQs}bm3495ov3R!)41W~r$^GU3tp+lm z_U`REwCj|?=f;ME%SZc zTNd{59G1S%8rA!rNf%UxI2uYzHYJ%E9S+vjW;vRZ;`*h$wrzP-*zswO{v2I5Pk8P= zbg}Ej`(LMTetFE}zjcjo-MX!Py3O;>%gksG6guQ3pnNm191NsjLmL$9yX6M44P z{QA}zGSga`StDmiH%Y8yn0@<~?uJ>X%~r<7mOqop$@zcZ-to#jzNoW{R9dD!7M7b?y74ySH9dTZ6X*x1+e-CQ)zWanr8Eht-;ZD{#EN=re9 zlbt))LPqGbncMwM?6JoRLkiv+q-KBl{#k4HwnK)CN+-&6nk-woB&Ovr7Ey4ioE@jv3HZmp zkLSzz;&#^lW^NDPp%Q1IsLxEwj;{_DT)cM z-W`=O<3+ji&6_zXayR_T z`y*49A9{av+Rbk-_pq;9(JQk(`<~$(t@m23Z!5D;t??+hoG%^wLr=@?;X|`~X$#no z)+dL&2vJJ??ECcdCZ;8aR!XN&Y%x$dfbB)qh`{-;=$v;cwKY znRcA>H`QCTtlpp^qw`Ue-PFiUORl)wkbTR;0*|j3vIEVU-Kxqr#U}e*|8<)G*~3d` zwx0Rf+j9MZm(aBJ+{(S%P82Md+v&Kw#VuBunLWos>WIClVJjb>hSLoHbmo;Ibq9V` zhBlr~@{vDk(HQqddB)u3e4Fn~6I~_E5b`w2(ErcOq@?RNf-BF7`#Yg{zBS(KhaOn#(A*+?_oXS}tDjeFW z_JU6;mrq;jv8O*xdg2mwt**Tf_MHB-u=m32qRNYZ?&fs@t79QU>uH)SCVZP8p<1_YMMSoR5 z7rZir2m0d@FV6{%VP@TQen3d@>dK zKjXXbGPR9@6Mc#WM51g&l@0!hRQ2r;^L?($K34jEmOvo5+*JC!wolIooetZ`?AqpMnU5VPqS5)_pem_ zXZT+IWVEiDk8hH4wfX`jrJ2tZL_e!2YHX>{lE1f0+b!XbW+9iI#uu4F-NR*@b!MIT zs<(ELx&DFPSq0ypM-;A$lq;TTe5Xi8b#l43e_&bBNAVKZ*;h*|?sV4qZ}YAfj<(&5nLkp2ZYom|<%1 zIg_J)eUiOsZgSdc?Zm)?FB7g@UzYlKZ%WE8W~H>=%m?WTr>}d6FBJ9klMnW;ES>1} zC;6WLX-*lxN!z1*mfoG=J7d-nxAh;`+`subxE@q#a&a*_>sTDY@1XU}(@D0c+c_ZP zV@Nuaaj4y%(s0ge+rxSe{0QE?$|~rQNKL@ix_yB!EFVSP4_1h7dz%)yv3*H|$;8*O z%)d2bqFnRia`o57H%Oa4++O(o!MpPtAKhG9{5WQ_&9m&A|DGGA?|90}TlK`kOYJ?s z!jpGl^A^2H{F3nY$s_$2mpk6R>@r*ZdT&hTs}DPb{~o_|@z1=R>Hk);h5pwOkoqNE zb>p|wk$FE$*Twwsc**d&zWLB6HQSaCg5ho-r#NzcS*(5f>#wQ3-_QQ{|CSJ2aWmX} z*A2eTKesF&+uh#tweVi|{LS|-DSo~C)Z6^d4W8`l@6%RZ+jisqmBvlFS6P-MT`)ek z;9^$6v&*qkDwhQQ#T`|jGUrI0)~#cn4id-DgoPjaZ8P=oqNd9Sr@jz8uwuRM>G?d}S=Q+^!ZGynYBx_Q%tE#@q&{54l4f9nik*7BKNyA5a8Ui>`Ez_hcMJ>I(~ z?jdhqX7~C2H**`hZhv>`Zt`U5+^T%6qa$Tj^M1aFre_=E8ZO?t(-?AQa!c~Ez*Z{> z@pj&#t8EJ|JX$$@m%@s3ENQEL<}F!WQ}KG8w~*#~p*8tyRgbM*BhmPAx&CY8Wo4nI zOC0UDFFm69W5H)?;;>t9v>^IKpTyR5Ypx3n?VBA+oi7`cvDB z?QN-B9$4Lt^6x)yuVGx?Ch+9dJ^GgK zSwETM>+#y<4p$`({K@sZFyB;ryK?VQiD~l})N83ry8qo(qu|IissD?>_ZJ>3Y$HTw zN7%1W-Znwj@bdSComuD0MEF;nTJw;-eqT$%VYW*J3*xqm%RGzRvGR+}eNms*=Z7vn zXsFBN(?4CE?NNSs5kK$g;~5)^HIK6-`YhOS^XrU!mkIwb|EUW|dnavvE6i7YC!dA` z>$YR>H!NA)!6PHJ;dW#F^>oXL6_>QX$mdOF{;p=7HeptChrRy9w-J*yLlRuJ7_$H1 zxxd0!ZrAyb%ii3d^sjS=o?E*3>=32`^*6gcJ`~>5_c>|M@bh-)bJyfK3+%Ih9&f%Z zo?&*dh`+1yw0Z24bpa1!9R7#+sm^%ut+=Cmo!EPGu^$diEOXWE_Nd6eRCKHeow@${ zgYHusVvMb~MZS%@_%w7wU4Z*l$v6LV6s?mu-P&(Ge`$20sjsi#%DlH48-E5)h!1`fSI>YERz9a(Wx3J?t(y>qh4N zKK$+W%ViVO1Laq4I8$|wr({av&a{tgtoIlg7#Pag4W_;0OVpXk8Muo*_lRUc)|dX5 z$xBV=rA%GSce}%=^3IMW53atHn||qL2j?~)?YfOMd!O&nWuLHLcB1Tdft0Kb3ZFjj zEc9sE=Xg)*)|J}ayH8$!xw18|`Eu)V;hY&^DcLIy{7w2<)s=ekjR<3me==Ln&0qYs z$-NxBU#;^LPWWUB#9d8re6lFLu*in_$whCLEAE$gTmLTQ+L~>;b;U2IO*3+C?>>2L z&7PkQMmKUEyWWo}y>pHC{OXIgk!m*$)&$;TusU%)uwnm&L@~uJU$%yAKB9bn*VLIi z_AceuVA?S+p0$JH7|+d38@XSq=;zn8M`ZX29!!*7y)8}Gw(O4M1jgHiY%?w??0J7x zK&xbt@88tKUw->#}(rW_LGbF8Gf2f<6P1AkZ*=WeHQO-j$B*ai77eLpC!jI zW@iU}k;zF+YE3n`@hOSHFNbaFTWQ9n)lD2<4u0i75}vx@<}sn|F9W*w?RfQPXGcxa zUD^Ahw{$&wF4uhieZ?o$cT-}7)z-jsi}x^;T-j~#$m{-6N81}yuP?cHB=_>QFTb5L zUuK!)-TbpQy~FKR!j20rEV{+U%(72carwmG<*hjxz*=ad&g8giAGd(ONgjo^@Qkgh z3i+*@ccxw8JeT-ne%!sEe3~~;&e(8ahSKrtE4D;#wiVag!rQogZ;bVUT{&yt?r2t+ zwSV(A=53F7N;Y1a_3Em-<=ji<>sfBAi5A}xYkr)}Ei@&?x`j74%dRRbYQ+=2^W2j; z|Ig)OSf^ggK5yd({>d)w9E&f>F@DO(WIOclds298Lu&eI$sDu7yll4T|E@@vbzatf zAaJWHD*dk4nV&oF=lAV<|5JSXKIeoDeOIqtTl`|-#mRLRHx3=~zyB%d;_md1%l3q) z+izvN>#@nq`v$M}t@T{eK4#2b-yB)067DCwKfEISeyCwy-+Q;reU+yZH}~vGYnD>Y zzqBAI79J>C0`PK{P4RvnL z>yEy+PIh+6p$%`6KdF^wEuO=aJDF<^C)>)`d}j7V?CGtn4B^64_j@%x+EFD^y-|A| z&o*g`NtgO&KfSt-x8~0K?cBHTE4E#}bmilfM@|`cH~)~o)tuM3Pwecsoodnf8_FL_ zYypY@o7b@v>bP%vjG}XPX>yFI-dkX8QikX;peh61{fBW=Jz>jocA$vMhWTmHp0ENXe1zvHyyCvJ9e zTs*Yf^Vj)hwnA3+&a1`H8ytLDi^o;uk*9dXp|NF&9pSkUZ=i?3>;egcN?sPBqxA_4)fE!$aFTWhRzRU|(P&K7Y?|zLwry zsw?kR+H87nQT(~`f7T0+eU3+^Ya{My*?g$n z`25-DfB*g-+JE)J#F{gw7uYs;nf;nIk$v~ZJs}k9p_`Uo+C8&} z^+(wkguDnz_Whiy#I))2Q|Xn5mKd~5OxK*hK<&xVbvypvoB#9L=QCF4U;M3ZSedo91)ct!}ZX*O5`#V9GA~QBKRvh~2Qf*rVX#mcZ-_U#r}j&5~m`m7o4~-S5)F zXZ%0UY(4YfdQ0#6X+mDxdX>57E+{zBvfHtfSvl5C$|8r|P}Kg21|MH*y8jHPx{#I3 zp_RW5_#~ZfY_vEkKSTLT+-APzb61H@yYn=JLHdusVbt}cq?zX`gKu~~^cN5KRTox$ zU3Y5I8JR1(y9oCFTI!!p?|Jb0Lhr%@fA1yTeD+>hWY5FByvr~AQR`~9 zx1YGFuHb^o&dk%IKb)SqSx5gZuey_(z4d5a)R%X`oaWy>EXsCpuG_d!X#R&T`!mLq zRsMpO!Z9!&$}(qexsu1^wl9*MbHyFD(}!NO^ww-*^%ra96As+Lf6=<1cY5nJp3wIX zIi-H4aNL+2%r#G!i#x{MRz_`cf$Wo$?(&Pu<>V4%HKp~r*GRohoGr2X{vpZCtzU&q zKb;W%KI6aOMzv)E#Xb{7ZK6a({uwBX?dYo#uX^HZ;p_ImvR~)A^~p(nR@^^!+Pl27 zv1@DLwmD!GWXlwgY8EFRVLqoe-}KgDGZTrGjE3R+TMVXN-E4F@`<1aE<6V{PrxR3H z_Agg|Z}?A5H~OSt?^M)#Z}YF^ zpt*`v21x6Y+K-{RN3yk8SuW^ZlF$tr&nonaWn zl=<1hG--W3M{=&HeWLd2w1k%j15=k>zmk%&_i>sM^RDyy2=n1k~wL73jxm{Yn!lOj@?E3i*Xrj!G?i|6@V#*RqmAd^JuY5)^O?=&nCJg)W*@Fnqu{-Y-^-*#Mnz1pnnRc6fIzrs5{{JD7P_`m5n^Ztjjt^6e= zp!54imGsYfN1T4dtSkM@@Y3Vcq2~GzEw*YO-NFUGa5_%;dRlw&_ui?$zWM(@d$S@o z;l?iW@LPX6`EJ`iw!ByPbwl+IRhm zQ~gKhpEy~4>?F6~$1^VMC1=}mozEY*EOm})r|!h{(;Oz`{wbfN?W8yPrH16RWp;C> zr-ZDYs`Tp9lm`t*=kGcGV_x0b^K&eOr_KFUxp2nTe3hBytirPmcYDqHe6hB-)6}5H zJD$Cd_hDTB`R>fFhPiLLoxb1hWbth3IHtU{c~(kCQv~1s2DuH-8t>e?*fRM{NNeD- z{9~iUGUM0! zOG-n_mTtFqT<}Bl$U-aU&x>pRu3EHj`rNHYiZ5(olYG9-Vfnu8O=mkdoxRPonSZ18 zMo<2v4c)1syRR!{>=N~e-x2(cZRf-}hI{UHZP_dHpm~2(+^u~xOx}Xp|2y`ir>^2W z(Ji!f{k#68>EY)0J{YNH$KRQI-D2m_EgJ7@IsCiw_dZCu&e+cAb#4ESPgytaKDb|Q z9FZObfu|HV>+U%v$Ai#V!x;w3;F>TnbY6I^Rz~Ocx3x@ zdV6YH`^CFf54N1|_m3(!*06u|M4&AwqWrPL+Z((;0{3Tp(EhQ|WBU9fQA!3{kLHE@ z*L&uFJN}hp@>PfBwYh%|NSMyQ;Mc3XU3>mCiKFUT^$T|Wb(dsvRHza7(m!d1$BXZ? zMIvmKSJ+1w%1+qUx$ygCk+SnyYfi1;-&fE6knM0nOWcBjOV4D)w|`l=Bhp9ozRkr$ z&s#I=8Xi=i*5^B1?vZ_(mw!?5#*E{MEXOr(?pWaCl0W0?pUeLzyh{tH3%g}5&9_tC zcN?pN#*z*1kIC?KEN;BLLCP}ydcF3giiwl+>uA^#n|y_li8uDhdH%>2C=OOwNoJ$CAI6<^A$%nYq? z?0#^4y>ZNjQ*R@;S%*Ho80Q{Px8eUA$*W1$iaEF1-8fGey?kEK*Vm-+cHWf7k zJqe4qiZHZV-p|=AZ=Cmxx9uRuR;y*XdmSDwVg1u~P~1~ffBjcKFC(S0&t3Bbr_Ipb zSD`TdrfkKRlimkB*6CgP%XlEmF>UGhX>l_CT9%cu^QTRJs{HYjZ?+`s*Xwl&&yz0p z#oycZ`VePdP}SC#<{1obH!ti_+|zO2@RdPwSijP>pNX@w=CQEvUAA{Wm|3JXa=w}z!<$~TUsl_pd%3KX4R_wQ8k z$=TscJ}SMJnCAX>t>sEr!MHPJzVF!1ee6uW`>Tct|Jzp3aoZJ3TpB8b55@@oeg1Ue9Xjje2sM-0WYBT^Uwit-Cmi%KekGg4Da92po`7@WbUFQAAA zB&Mf2GB7y%`#OjCK)3EQXfOw-miQzVm$-tri!*pbb;5V&J2EiDF}kOgfHoR~HhE_z zm6oIyJ2Eh2Frl15!4Lw~2~r3>f5MT0!Gys%C%-t=BQY-}CzU}SWE)69NM>#-LqCH{ zez8kpNh*jJk`EUz1lfn=reGwK9T^IlL-TTxPrqO|!<@PQnV5W{B%$S!$b$SD_&3=GL2J3y`t2Ce414B~~P<`#fNKzlg-t+FW`0p-NhNguvlB?4b7@gg zYF-J*Q>n$p46aCGFug7yy$}w}1co$ds6q8PGBDJFbV6K#E25Y9^)Vvg^UPlH7EvBGU z@J2`%X9gaSDK5?o3&HzxK?^hy>Q*s@6eZ>rgHAbt3NkP-Fa@NhW<$A-3=Ah2T=P-_ z5fAq~`jjmK0?s7c(&MGB~BCXXZgw^f9=kriFv|rh@|1k%3_kQ)od- zVo54Qkbz+mQ*cIpIgFP84SfjT736ECAm|QwaO65NFzjLkrA+72qT>7_M}{|yzWHTv z(@H=tf@ls(O)g-=>kbr|L5d@h46=Y!W1%)ldDu&Qt z*C1H_H-@^?#nUOXBsZ~u0hBAj=>fWNj==_`6BHfJ`8oMT3=DouAdY`2sGJc4$vI{F z=BK1G@G(L5aKrRjLQQtd2c?w&C@(laCo{#VsI)kPfx+Fy6Y7j_j6tdCfu*TMmEaB8 zjtmT^K_cLy!#lN-;X8S`V#fo&&2LnTXPAb%&;jG0aMadaO3=I0Lxv9Cy1(gg8#vm4Wfpj8BHZM7`gdr5f z0x=j8K&+h9JO+j+5DToMiYY!PKRGd{Br`YF%!GmAB6B>HnFmr+lApuC@RlX5C^eOV zL4`FpF()TKnSsHKIUaOMLS7leX2$rm;*!J?Gm}hXBZg-zX^;%au!tc(J~KZlKCLt_ znSsHD6|zN?f#D6vti%+CuPkXLsW~|epIOq1Q&Y1Up0K2WHpwyEWJyZ~74i%WcUaQW za=>x>4x|SxTgV(=oCmcb1?0XwkS8KQtmNDR28Ij}3mhndEb(P{ttn^NUItoLP%fA>O{vl9rxY zQp}JH(wPUcu$v)1J`sEvNEd{a%)sCq9OfKkVE{f>L4q+pF}FB99<)=Ofnfnde0+Lp zNp50hUSd&tF@rv1d@@9)o)MxXogqFVxd2o@mZTOjXfwrU7Uw1<<$%ldYb+48%#87g z$+;;xc??`k@tJvJ!5<_Xoqcj3AjQ4$8ajcONRLP;?$DT;#AN{ zEg9gwK?3>X;lm_zd*?gE*gS^zrX$SpGm z6oCw0pt=;Nv?Y^gu?sYq0*X?LQ}ap~7Bhj1rhwF$wm2~ z121AAHFa?eG!~)9!o+~;3h@4D2GFfn6G|KyK(}u;lsGU*F)%O`lsGU*g3huiabN&l z+GtVYzyLZMTA{=Nd|9GEi30=ZnzSFq4h*2tgBQgP44`wb?-V;QoGEr-m@ba!+ixj$ zU?>qo@Rt-jFnpDCU|@lIa!RoS1G61MzMu~(2txy?e_g=90J_*0Y#BryBLnERkq!)T7KUsF&=?>?18CZO2LnR` zXxQWe149Ey{R<5Bpu20phYetu53)=GsRInNmy?Bo0dz+yNFIcd#Vv5C_rM_@fkQll zk%6J&A>>G%ClCu58eW6u$`}}6e1*3R3@l6tzRG(BhA1R{!$$@N5k`dklurx{K}dWJ z1_p2#>VUluv19@0-WrgnkT7Uj!1L$N8SdS?$8hc1HHN)=_cFA!v@ke1If2eWWiT`} zWYE;qWRQ@MU=R=xVBp~3U|_iSpJ8b-#E^I70S5B4y&G!!-v9p(^67ifkjpzH@;`<+ zSRLGd|NsC0_wL<0khcGzp_>0t5>p(k4yF%e!@qYBi@i@5x$RH5u=oAeS|M%`aNId%guOLT9 zr)ZG*?_l0OG%c>-**4_5c6> z-+_jXj)qZzhK7b9($O&rNjwTHE8vA{pMdDHW&6J=h%Q?yASxgNvQR)IwX|)SfY|^4 z|Gz1K*3W)t*Du8L9qanz2kbNFO5fJ+X+#Lj?eg#E<>=tl$@cI>q z;Xjeo6aiHMi2uZvEd%*7HAO&FfI)PsfT{pUR#ZTAssIBv`=Y?%39;1!$`&*PhcG0Z z9Sza#QvmsIJt&?;mno?I|E8b@Ws3-aV-ymv3?gX$`wdD8kTennN+Z96qCsgS>UU6N z5I6-v(ved*T6lsI=Cb|&K-B*K+dz!{F!nZZiUlQ1kO-Q6@Bjb*^Z)-pP__W2w|5{O z41=yH1GE4C{|`z($m#msJ7|u8(jXoTgYyVDgX{xi6;6EsjxCg@1CW^jqpgaaDRX}A6 z)ILZK6#Ead4^##~^8$)Ku<4*uB{I-38XR_D5x9MVhVZnBq7URhP}u^qQVnV!G&h3n zgOmo4$U~H8p!oCr^V0zmdY}-5VTcGg-^20(R{KD{5d($eGI(hN;t7bZ2Zb><`ve_f zC5%Uqqktf|EJIH(nEJkfb37!ki-On;ssi9NiIP4s^+9qxB+W+!IXXHzMFoNL07`xU zc@2!==?s*am%;M-wq>B22Q}aS1C;|{Js<+E50=&cBUggxY>*BJhU`!Z98k-^#qga$gy99~R@DDZ3?U2|44GgZ;S8A!RSby?MGPqn3Jm28nG87$ISdL6 znP3x37!sj;Rj}%OhCBuZhDwHfhEj$i1_g#>hJ1!xh609Ch7tyl&3X(949*NW44Dkc z4A~3{41Nr$3>6F|30B(@Pmr859`u!R9!FeUQwM3bq@f7Nj#1sy73y z7U6#d24r``Ze4(Qn1y3QKrMqj!*_;ehQDC{HbNvoBohMz9}`0W8v_SJ2E$?oMivH! z|Nom9{{IK{NI(i;m?@x^VKKvZ&_TpdH6R)E_~8HvGEggic;N8^@@Wo3Dnl~Z{{;+{ z3?&R143N-AjhRGnXhLKl;a18}%#g}p1$G&XXy9lJ*w4_xu$6(2;XQhofE9uK+sR!3~@`LAfCx z9O|HSt-zoMh9D=PrvYl_(Qgd$@HBugA0;uQGNgmu3CdZJ+<_}yfbyg#LmE8af^s>i zyg=kiP`s8gWHO{Ol*4mmGD8M9Po{%&9!Py9SPo$hDF5TKQvsX?OTcL}k0G6*m_Y}e z`w=CB6GI}ni~zX~*{8&n1h{iQD25sWY8mD-d}H8ac+JefkjqdE&PO>6#SHNb1q?+D z`3&jcv|P+k%wWu51X@FYCTa*W1_?7VK=Q{o1`dWd;B?u;pt=KAlc1Eyc>N6a zAeexZ+Zy0<8xqElyl<0OT%4MllvAltk(-lOY^zjSlxJ0(oRONFSge~1TSKCooS$oz zSe&a@W~c-@(lj$IwYUVjki=HWP|rZgj+aY8!6qfOAT=)~H7~gm%tsO_afI3fl~7QC z+w7T=npcupQmFu1?`5l0o|$KCq@(~kpx;&rzKTUJ%-O;~&p9YW33l?Rt&)YFfu5;> znSq6#fs#T&Q9fw>RDO|TQF2CRNosOQX;G@JQiX+?l0rdgQch;FcWPxwes*e}t&(9{ zvPF_{l4YV{s-Z=yagvgqjXo?KV6K6>(?%cWCsZFG#KC?==(h`S3~+RD@$_?#h=4Mn z)F>Ys5Kw9~JfHyqrAEU88W2!wG(4aI0i{O60~!!eYBW5c0Rg2(!vh)+P--+hpaB7; bM#BRd5Kw9~JfHyqrAEU88W2#5>fr$Z12R5c literal 0 HcmV?d00001 diff --git a/Lib/packaging/command/wininst-9.0-amd64.exe b/Lib/packaging/command/wininst-9.0-amd64.exe new file mode 100644 index 0000000000000000000000000000000000000000..11d8011c717c3a1c54afbe00e002fab6d37766c6 GIT binary patch literal 223744 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

3_$B$ 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%0D5Z6R2EACo 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 .o/.obj) + # * library files (shared or static) are named by plugging the + # library name and extension into a format string, eg. + # "lib%s.%s" % (lib_name, ".a") for Unix static libraries + # * executables are named by appending an extension (possibly + # empty) to the program name: eg. progname + ".exe" for + # Windows + # + # To reduce redundant code, these methods expect to find + # several attributes in the current object (presumably defined + # as class attributes): + # * src_extensions - + # list of C/C++ source file extensions, eg. ['.c', '.cpp'] + # * obj_extension - + # object file extension, eg. '.o' or '.obj' + # * static_lib_extension - + # extension for static library files, eg. '.a' or '.lib' + # * shared_lib_extension - + # extension for shared library/object files, eg. '.so', '.dll' + # * static_lib_format - + # format string for generating static library filenames, + # eg. 'lib%s.%s' or '%s.%s' + # * shared_lib_format + # format string for generating shared library filenames + # (probably same as static_lib_format, since the extension + # is one of the intended parameters to the format string) + # * exe_extension - + # extension for executable files, eg. '' or '.exe' + + def object_filenames(self, source_filenames, strip_dir=False, output_dir=''): + if output_dir is None: + output_dir = '' + obj_names = [] + for src_name in source_filenames: + base, ext = os.path.splitext(src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + raise UnknownFileError("unknown file type '%s' (from '%s')" % + (ext, src_name)) + if strip_dir: + base = os.path.basename(base) + obj_names.append(os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + + def shared_object_filename(self, basename, strip_dir=False, output_dir=''): + assert output_dir is not None + if strip_dir: + basename = os.path.basename(basename) + return os.path.join(output_dir, basename + self.shared_lib_extension) + + def executable_filename(self, basename, strip_dir=False, output_dir=''): + assert output_dir is not None + if strip_dir: + basename = os.path.basename(basename) + return os.path.join(output_dir, basename + (self.exe_extension or '')) + + def library_filename(self, libname, lib_type='static', # or 'shared' + strip_dir=False, output_dir=''): + assert output_dir is not None + if lib_type not in ("static", "shared", "dylib"): + raise ValueError( + "'lib_type' must be 'static', 'shared' or 'dylib'") + fmt = getattr(self, lib_type + "_lib_format") + ext = getattr(self, lib_type + "_lib_extension") + + dir, base = os.path.split(libname) + filename = fmt % (base, ext) + if strip_dir: + dir = '' + + return os.path.join(output_dir, dir, filename) + + + # -- Utility methods ----------------------------------------------- + + def execute(self, func, args, msg=None, level=1): + execute(func, args, msg, self.dry_run) + + def spawn(self, cmd): + spawn(cmd, dry_run=self.dry_run) + + def move_file(self, src, dst): + logger.info("moving %r to %r", src, dst) + if self.dry_run: + return + return move(src, dst) + + def mkpath(self, name, mode=0o777): + name = os.path.normpath(name) + if os.path.isdir(name) or name == '': + return + if self.dry_run: + head = '' + for part in name.split(os.sep): + logger.info("created directory %s%s", head, part) + head += part + os.sep + return + os.makedirs(name, mode) diff --git a/Lib/packaging/compiler/cygwinccompiler.py b/Lib/packaging/compiler/cygwinccompiler.py new file mode 100644 index 0000000000..7bfa611ea5 --- /dev/null +++ b/Lib/packaging/compiler/cygwinccompiler.py @@ -0,0 +1,355 @@ +"""CCompiler implementations for Cygwin and mingw32 versions of GCC. + +This module contains the CygwinCCompiler class, a subclass of +UnixCCompiler that handles the Cygwin port of the GNU C compiler to +Windows, and the Mingw32CCompiler class which handles the mingw32 port +of GCC (same as cygwin in no-cygwin mode). +""" + +# problems: +# +# * if you use a msvc compiled python version (1.5.2) +# 1. you have to insert a __GNUC__ section in its config.h +# 2. you have to generate a import library for its dll +# - create a def-file for python??.dll +# - create a import library using +# dlltool --dllname python15.dll --def python15.def \ +# --output-lib libpython15.a +# +# see also http://starship.python.net/crew/kernr/mingw32/Notes.html +# +# * We put export_symbols in a def-file, and don't use +# --export-all-symbols because it doesn't worked reliable in some +# tested configurations. And because other windows compilers also +# need their symbols specified this no serious problem. +# +# tested configurations: +# +# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works +# (after patching python's config.h and for C++ some other include files) +# see also http://starship.python.net/crew/kernr/mingw32/Notes.html +# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works +# (ld doesn't support -shared, so we use dllwrap) +# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now +# - its dllwrap doesn't work, there is a bug in binutils 2.10.90 +# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html +# - using gcc -mdll instead dllwrap doesn't work without -static because +# it tries to link against dlls instead their import libraries. (If +# it finds the dll first.) +# By specifying -static we force ld to link against the import libraries, +# this is windows standard and there are normally not the necessary symbols +# in the dlls. +# *** only the version of June 2000 shows these problems +# * cygwin gcc 3.2/ld 2.13.90 works +# (ld supports -shared) +# * mingw gcc 3.2/ld 2.13 works +# (ld supports -shared) + + +import os +import sys +import copy + +from packaging import logger +from packaging.compiler.unixccompiler import UnixCCompiler +from packaging.util import write_file +from packaging.errors import PackagingExecError, CompileError, UnknownFileError +from packaging.util import get_compiler_versions +import sysconfig + + +def get_msvcr(): + """Include the appropriate MSVC runtime library if Python was built + with MSVC 7.0 or later. + """ + msc_pos = sys.version.find('MSC v.') + if msc_pos != -1: + msc_ver = sys.version[msc_pos+6:msc_pos+10] + if msc_ver == '1300': + # MSVC 7.0 + return ['msvcr70'] + elif msc_ver == '1310': + # MSVC 7.1 + return ['msvcr71'] + elif msc_ver == '1400': + # VS2005 / MSVC 8.0 + return ['msvcr80'] + elif msc_ver == '1500': + # VS2008 / MSVC 9.0 + return ['msvcr90'] + else: + raise ValueError("Unknown MS Compiler version %s " % msc_ver) + + +class CygwinCCompiler(UnixCCompiler): + """ Handles the Cygwin port of the GNU C compiler to Windows. + """ + name = 'cygwin' + description = 'Cygwin port of GNU C Compiler for Win32' + obj_extension = ".o" + static_lib_extension = ".a" + shared_lib_extension = ".dll" + static_lib_format = "lib%s%s" + shared_lib_format = "%s%s" + exe_extension = ".exe" + + def __init__(self, verbose=0, dry_run=False, force=False): + + UnixCCompiler.__init__(self, verbose, dry_run, force) + + status, details = check_config_h() + logger.debug("Python's GCC status: %s (details: %s)", status, details) + if status is not CONFIG_H_OK: + self.warn( + "Python's pyconfig.h doesn't seem to support your compiler. " + "Reason: %s. " + "Compiling may fail because of undefined preprocessor macros." + % details) + + self.gcc_version, self.ld_version, self.dllwrap_version = \ + get_compiler_versions() + logger.debug(self.name + ": gcc %s, ld %s, dllwrap %s\n", + self.gcc_version, + self.ld_version, + self.dllwrap_version) + + # ld_version >= "2.10.90" and < "2.13" should also be able to use + # gcc -mdll instead of dllwrap + # Older dllwraps had own version numbers, newer ones use the + # same as the rest of binutils ( also ld ) + # dllwrap 2.10.90 is buggy + if self.ld_version >= "2.10.90": + self.linker_dll = "gcc" + else: + self.linker_dll = "dllwrap" + + # ld_version >= "2.13" support -shared so use it instead of + # -mdll -static + if self.ld_version >= "2.13": + shared_option = "-shared" + else: + shared_option = "-mdll -static" + + # Hard-code GCC because that's what this is all about. + # XXX optimization, warnings etc. should be customizable. + self.set_executables(compiler='gcc -mcygwin -O -Wall', + compiler_so='gcc -mcygwin -mdll -O -Wall', + compiler_cxx='g++ -mcygwin -O -Wall', + linker_exe='gcc -mcygwin', + linker_so=('%s -mcygwin %s' % + (self.linker_dll, shared_option))) + + # cygwin and mingw32 need different sets of libraries + if self.gcc_version == "2.91.57": + # cygwin shouldn't need msvcrt, but without the dlls will crash + # (gcc version 2.91.57) -- perhaps something about initialization + self.dll_libraries=["msvcrt"] + self.warn( + "Consider upgrading to a newer version of gcc") + else: + # Include the appropriate MSVC runtime library if Python was built + # with MSVC 7.0 or later. + self.dll_libraries = get_msvcr() + + def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): + """Compile the source by spawning GCC and windres if needed.""" + if ext == '.rc' or ext == '.res': + # gcc needs '.res' and '.rc' compiled to object files !!! + try: + self.spawn(["windres", "-i", src, "-o", obj]) + except PackagingExecError as msg: + raise CompileError(msg) + else: # for other files use the C-compiler + try: + self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + + extra_postargs) + except PackagingExecError as msg: + raise CompileError(msg) + + def link(self, target_desc, objects, output_filename, output_dir=None, + libraries=None, library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=False, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + """Link the objects.""" + # use separate copies, so we can modify the lists + extra_preargs = copy.copy(extra_preargs or []) + libraries = copy.copy(libraries or []) + objects = copy.copy(objects or []) + + # Additional libraries + libraries.extend(self.dll_libraries) + + # handle export symbols by creating a def-file + # with executables this only works with gcc/ld as linker + if ((export_symbols is not None) and + (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): + # (The linker doesn't do anything if output is up-to-date. + # So it would probably better to check if we really need this, + # but for this we had to insert some unchanged parts of + # UnixCCompiler, and this is not what we want.) + + # we want to put some files in the same directory as the + # object files are, build_temp doesn't help much + # where are the object files + temp_dir = os.path.dirname(objects[0]) + # name of dll to give the helper files the same base name + dll_name, dll_extension = os.path.splitext( + os.path.basename(output_filename)) + + # generate the filenames for these files + def_file = os.path.join(temp_dir, dll_name + ".def") + lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a") + + # Generate .def file + contents = [ + "LIBRARY %s" % os.path.basename(output_filename), + "EXPORTS"] + for sym in export_symbols: + contents.append(sym) + self.execute(write_file, (def_file, contents), + "writing %s" % def_file) + + # next add options for def-file and to creating import libraries + + # dllwrap uses different options than gcc/ld + if self.linker_dll == "dllwrap": + extra_preargs.extend(("--output-lib", lib_file)) + # for dllwrap we have to use a special option + extra_preargs.extend(("--def", def_file)) + # we use gcc/ld here and can be sure ld is >= 2.9.10 + else: + # doesn't work: bfd_close build\...\libfoo.a: Invalid operation + #extra_preargs.extend(("-Wl,--out-implib,%s" % lib_file)) + # for gcc/ld the def-file is specified as any object files + objects.append(def_file) + + #end: if ((export_symbols is not None) and + # (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): + + # who wants symbols and a many times larger output file + # should explicitly switch the debug mode on + # otherwise we let dllwrap/ld strip the output file + # (On my machine: 10KB < stripped_file < ??100KB + # unstripped_file = stripped_file + XXX KB + # ( XXX=254 for a typical python extension)) + if not debug: + extra_preargs.append("-s") + + UnixCCompiler.link(self, target_desc, objects, output_filename, + output_dir, libraries, library_dirs, + runtime_library_dirs, + None, # export_symbols, we do this in our def-file + debug, extra_preargs, extra_postargs, build_temp, + target_lang) + + # -- Miscellaneous methods ----------------------------------------- + + def object_filenames(self, source_filenames, strip_dir=False, + output_dir=''): + """Adds supports for rc and res files.""" + if output_dir is None: + output_dir = '' + obj_names = [] + for src_name in source_filenames: + # use normcase to make sure '.rc' is really '.rc' and not '.RC' + base, ext = os.path.splitext(os.path.normcase(src_name)) + if ext not in (self.src_extensions + ['.rc','.res']): + raise UnknownFileError("unknown file type '%s' (from '%s')" % (ext, src_name)) + if strip_dir: + base = os.path.basename (base) + if ext in ('.res', '.rc'): + # these need to be compiled to object files + obj_names.append (os.path.join(output_dir, + base + ext + self.obj_extension)) + else: + obj_names.append (os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + +# the same as cygwin plus some additional parameters +class Mingw32CCompiler(CygwinCCompiler): + """ Handles the Mingw32 port of the GNU C compiler to Windows. + """ + name = 'mingw32' + description = 'MinGW32 compiler' + + def __init__(self, verbose=0, dry_run=False, force=False): + + CygwinCCompiler.__init__ (self, verbose, dry_run, force) + + # ld_version >= "2.13" support -shared so use it instead of + # -mdll -static + if self.ld_version >= "2.13": + shared_option = "-shared" + else: + shared_option = "-mdll -static" + + # A real mingw32 doesn't need to specify a different entry point, + # but cygwin 2.91.57 in no-cygwin-mode needs it. + if self.gcc_version <= "2.91.57": + entry_point = '--entry _DllMain@12' + else: + entry_point = '' + + self.set_executables(compiler='gcc -mno-cygwin -O -Wall', + compiler_so='gcc -mno-cygwin -mdll -O -Wall', + compiler_cxx='g++ -mno-cygwin -O -Wall', + linker_exe='gcc -mno-cygwin', + linker_so='%s -mno-cygwin %s %s' + % (self.linker_dll, shared_option, + entry_point)) + # Maybe we should also append -mthreads, but then the finished + # dlls need another dll (mingwm10.dll see Mingw32 docs) + # (-mthreads: Support thread-safe exception handling on `Mingw32') + + # no additional libraries needed + self.dll_libraries=[] + + # Include the appropriate MSVC runtime library if Python was built + # with MSVC 7.0 or later. + self.dll_libraries = get_msvcr() + +# Because these compilers aren't configured in Python's pyconfig.h file by +# default, we should at least warn the user if he is using a unmodified +# version. + +CONFIG_H_OK = "ok" +CONFIG_H_NOTOK = "not ok" +CONFIG_H_UNCERTAIN = "uncertain" + +def check_config_h(): + """Check if the current Python installation appears amenable to building + extensions with GCC. + + Returns a tuple (status, details), where 'status' is one of the following + constants: + + - CONFIG_H_OK: all is well, go ahead and compile + - CONFIG_H_NOTOK: doesn't look good + - CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h + + 'details' is a human-readable string explaining the situation. + + Note there are two ways to conclude "OK": either 'sys.version' contains + the string "GCC" (implying that this Python was built with GCC), or the + installed "pyconfig.h" contains the string "__GNUC__". + """ + + # XXX since this function also checks sys.version, it's not strictly a + # "pyconfig.h" check -- should probably be renamed... + # if sys.version contains GCC then python was compiled with GCC, and the + # pyconfig.h file should be OK + if "GCC" in sys.version: + return CONFIG_H_OK, "sys.version mentions 'GCC'" + + # let's see if __GNUC__ is mentioned in python.h + fn = sysconfig.get_config_h_filename() + try: + with open(fn) as config_h: + if "__GNUC__" in config_h.read(): + return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn + else: + return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn + except IOError as exc: + return (CONFIG_H_UNCERTAIN, + "couldn't read '%s': %s" % (fn, exc.strerror)) diff --git a/Lib/packaging/compiler/extension.py b/Lib/packaging/compiler/extension.py new file mode 100644 index 0000000000..66f6e9a6bb --- /dev/null +++ b/Lib/packaging/compiler/extension.py @@ -0,0 +1,121 @@ +"""Class representing C/C++ extension modules.""" + +from packaging import logger + +# This class is really only used by the "build_ext" command, so it might +# make sense to put it in distutils.command.build_ext. However, that +# module is already big enough, and I want to make this class a bit more +# complex to simplify some common cases ("foo" module in "foo.c") and do +# better error-checking ("foo.c" actually exists). +# +# Also, putting this in build_ext.py means every setup script would have to +# import that large-ish module (indirectly, through distutils.core) in +# order to do anything. + + +class Extension: + """Just a collection of attributes that describes an extension + module and everything needed to build it (hopefully in a portable + way, but there are hooks that let you be as unportable as you need). + + Instance attributes: + name : string + the full name of the extension, including any packages -- ie. + *not* a filename or pathname, but Python dotted name + sources : [string] + list of source filenames, relative to the distribution root + (where the setup script lives), in Unix form (slash-separated) + for portability. Source files may be C, C++, SWIG (.i), + platform-specific resource files, or whatever else is recognized + by the "build_ext" command as source for a Python extension. + include_dirs : [string] + list of directories to search for C/C++ header files (in Unix + form for portability) + define_macros : [(name : string, value : string|None)] + list of macros to define; each macro is defined using a 2-tuple, + where 'value' is either the string to define it to or None to + define it without a particular value (equivalent of "#define + FOO" in source or -DFOO on Unix C compiler command line) + undef_macros : [string] + list of macros to undefine explicitly + library_dirs : [string] + list of directories to search for C/C++ libraries at link time + libraries : [string] + list of library names (not filenames or paths) to link against + runtime_library_dirs : [string] + list of directories to search for C/C++ libraries at run time + (for shared extensions, this is when the extension is loaded) + extra_objects : [string] + list of extra files to link with (eg. object files not implied + by 'sources', static library that must be explicitly specified, + binary resource files, etc.) + extra_compile_args : [string] + any extra platform- and compiler-specific information to use + when compiling the source files in 'sources'. For platforms and + compilers where "command line" makes sense, this is typically a + list of command-line arguments, but for other platforms it could + be anything. + extra_link_args : [string] + any extra platform- and compiler-specific information to use + when linking object files together to create the extension (or + to create a new static Python interpreter). Similar + interpretation as for 'extra_compile_args'. + export_symbols : [string] + list of symbols to be exported from a shared extension. Not + used on all platforms, and not generally necessary for Python + extensions, which typically export exactly one symbol: "init" + + extension_name. + swig_opts : [string] + any extra options to pass to SWIG if a source file has the .i + extension. + depends : [string] + list of files that the extension depends on + language : string + extension language (i.e. "c", "c++", "objc"). Will be detected + from the source extensions if not provided. + optional : boolean + specifies that a build failure in the extension should not abort the + build process, but simply not install the failing extension. + """ + + # **kwargs are allowed so that a warning is emitted instead of an + # exception + def __init__(self, name, sources, include_dirs=None, define_macros=None, + undef_macros=None, library_dirs=None, libraries=None, + runtime_library_dirs=None, extra_objects=None, + extra_compile_args=None, extra_link_args=None, + export_symbols=None, swig_opts=None, depends=None, + language=None, optional=None, **kw): + if not isinstance(name, str): + raise AssertionError("'name' must be a string") + + if not isinstance(sources, list): + raise AssertionError("'sources' must be a list of strings") + + for v in sources: + if not isinstance(v, str): + raise AssertionError("'sources' must be a list of strings") + + self.name = name + self.sources = sources + self.include_dirs = include_dirs or [] + self.define_macros = define_macros or [] + self.undef_macros = undef_macros or [] + self.library_dirs = library_dirs or [] + self.libraries = libraries or [] + self.runtime_library_dirs = runtime_library_dirs or [] + self.extra_objects = extra_objects or [] + self.extra_compile_args = extra_compile_args or [] + self.extra_link_args = extra_link_args or [] + self.export_symbols = export_symbols or [] + self.swig_opts = swig_opts or [] + self.depends = depends or [] + self.language = language + self.optional = optional + + # If there are unknown keyword options, warn about them + if len(kw) > 0: + options = [repr(option) for option in kw] + options = ', '.join(sorted(options)) + logger.warning( + 'unknown arguments given to Extension: %s', options) diff --git a/Lib/packaging/compiler/msvc9compiler.py b/Lib/packaging/compiler/msvc9compiler.py new file mode 100644 index 0000000000..d30444697b --- /dev/null +++ b/Lib/packaging/compiler/msvc9compiler.py @@ -0,0 +1,720 @@ +"""CCompiler implementation for the Microsoft Visual Studio 2008 compiler. + +The MSVCCompiler class is compatible with VS 2005 and VS 2008. Legacy +support for older versions of VS are in the msvccompiler module. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) +# ported to VS2005 and VS 2008 by Christian Heimes +import os +import subprocess +import sys +import re + +from packaging.errors import (PackagingExecError, PackagingPlatformError, + CompileError, LibError, LinkError) +from packaging.compiler.ccompiler import CCompiler +from packaging.compiler import gen_lib_options +from packaging import logger +from packaging.util import get_platform + +import winreg + +RegOpenKeyEx = winreg.OpenKeyEx +RegEnumKey = winreg.EnumKey +RegEnumValue = winreg.EnumValue +RegError = winreg.error + +HKEYS = (winreg.HKEY_USERS, + winreg.HKEY_CURRENT_USER, + winreg.HKEY_LOCAL_MACHINE, + winreg.HKEY_CLASSES_ROOT) + +VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" +WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" +NET_BASE = r"Software\Microsoft\.NETFramework" + +# A map keyed by get_platform() return values to values accepted by +# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is +# the param to cross-compile on x86 targetting amd64.) +PLAT_TO_VCVARS = { + 'win32' : 'x86', + 'win-amd64' : 'amd64', + 'win-ia64' : 'ia64', +} + + +class Reg: + """Helper class to read values from the registry + """ + + def get_value(cls, path, key): + for base in HKEYS: + d = cls.read_values(base, path) + if d and key in d: + return d[key] + raise KeyError(key) + get_value = classmethod(get_value) + + def read_keys(cls, base, key): + """Return list of registry keys.""" + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + L = [] + i = 0 + while True: + try: + k = RegEnumKey(handle, i) + except RegError: + break + L.append(k) + i += 1 + return L + read_keys = classmethod(read_keys) + + def read_values(cls, base, key): + """Return dict of registry keys and values. + + All names are converted to lowercase. + """ + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + d = {} + i = 0 + while True: + try: + name, value, type = RegEnumValue(handle, i) + except RegError: + break + name = name.lower() + d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) + i += 1 + return d + read_values = classmethod(read_values) + + def convert_mbcs(s): + dec = getattr(s, "decode", None) + if dec is not None: + try: + s = dec("mbcs") + except UnicodeError: + pass + return s + convert_mbcs = staticmethod(convert_mbcs) + +class MacroExpander: + + def __init__(self, version): + self.macros = {} + self.vsbase = VS_BASE % version + self.load_macros(version) + + def set_macro(self, macro, path, key): + self.macros["$(%s)" % macro] = Reg.get_value(path, key) + + def load_macros(self, version): + self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") + self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") + self.set_macro("FrameworkDir", NET_BASE, "installroot") + try: + if version >= 8.0: + self.set_macro("FrameworkSDKDir", NET_BASE, + "sdkinstallrootv2.0") + else: + raise KeyError("sdkinstallrootv2.0") + except KeyError: + raise PackagingPlatformError( + """Python was built with Visual Studio 2008; +extensions must be built with a compiler than can generate compatible binaries. +Visual Studio 2008 was not found on this system. If you have Cygwin installed, +you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") + + if version >= 9.0: + self.set_macro("FrameworkVersion", self.vsbase, "clr version") + self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") + else: + p = r"Software\Microsoft\NET Framework Setup\Product" + for base in HKEYS: + try: + h = RegOpenKeyEx(base, p) + except RegError: + continue + key = RegEnumKey(h, 0) + d = Reg.get_value(base, r"%s\%s" % (p, key)) + self.macros["$(FrameworkVersion)"] = d["version"] + + def sub(self, s): + for k, v in self.macros.items(): + s = s.replace(k, v) + return s + +def get_build_version(): + """Return the version of MSVC that was used to build Python. + + For Python 2.3 and up, the version number is included in + sys.version. For earlier versions, assume the compiler is MSVC 6. + """ + prefix = "MSC v." + i = sys.version.find(prefix) + if i == -1: + return 6 + i = i + len(prefix) + s, rest = sys.version[i:].split(" ", 1) + majorVersion = int(s[:-2]) - 6 + minorVersion = int(s[2:3]) / 10.0 + # I don't think paths are affected by minor version in version 6 + if majorVersion == 6: + minorVersion = 0 + if majorVersion >= 6: + return majorVersion + minorVersion + # else we don't know what version of the compiler this is + return None + +def normalize_and_reduce_paths(paths): + """Return a list of normalized paths with duplicates removed. + + The current order of paths is maintained. + """ + # Paths are normalized so things like: /a and /a/ aren't both preserved. + reduced_paths = [] + for p in paths: + np = os.path.normpath(p) + # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. + if np not in reduced_paths: + reduced_paths.append(np) + return reduced_paths + +def removeDuplicates(variable): + """Remove duplicate values of an environment variable. + """ + oldList = variable.split(os.pathsep) + newList = [] + for i in oldList: + if i not in newList: + newList.append(i) + newVariable = os.pathsep.join(newList) + return newVariable + +def find_vcvarsall(version): + """Find the vcvarsall.bat file + + At first it tries to find the productdir of VS 2008 in the registry. If + that fails it falls back to the VS90COMNTOOLS env var. + """ + vsbase = VS_BASE % version + try: + productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, + "productdir") + except KeyError: + logger.debug("Unable to find productdir in registry") + productdir = None + + if not productdir or not os.path.isdir(productdir): + toolskey = "VS%0.f0COMNTOOLS" % version + toolsdir = os.environ.get(toolskey, None) + + if toolsdir and os.path.isdir(toolsdir): + productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") + productdir = os.path.abspath(productdir) + if not os.path.isdir(productdir): + logger.debug("%s is not a valid directory", productdir) + return None + else: + logger.debug("env var %s is not set or invalid", toolskey) + if not productdir: + logger.debug("no productdir found") + return None + vcvarsall = os.path.join(productdir, "vcvarsall.bat") + if os.path.isfile(vcvarsall): + return vcvarsall + logger.debug("unable to find vcvarsall.bat") + return None + +def query_vcvarsall(version, arch="x86"): + """Launch vcvarsall.bat and read the settings from its environment + """ + vcvarsall = find_vcvarsall(version) + interesting = set(("include", "lib", "libpath", "path")) + result = {} + + if vcvarsall is None: + raise PackagingPlatformError("Unable to find vcvarsall.bat") + logger.debug("calling 'vcvarsall.bat %s' (version=%s)", arch, version) + popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + stdout, stderr = popen.communicate() + if popen.wait() != 0: + raise PackagingPlatformError(stderr.decode("mbcs")) + + stdout = stdout.decode("mbcs") + for line in stdout.split("\n"): + line = Reg.convert_mbcs(line) + if '=' not in line: + continue + line = line.strip() + key, value = line.split('=', 1) + key = key.lower() + if key in interesting: + if value.endswith(os.pathsep): + value = value[:-1] + result[key] = removeDuplicates(value) + + if len(result) != len(interesting): + raise ValueError(str(list(result))) + + return result + +# More globals +VERSION = get_build_version() +if VERSION < 8.0: + raise PackagingPlatformError("VC %0.1f is not supported by this module" % VERSION) +# MACROS = MacroExpander(VERSION) + +class MSVCCompiler(CCompiler) : + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + name = 'msvc' + description = 'Microsoft Visual C++' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + def __init__(self, verbose=0, dry_run=False, force=False): + CCompiler.__init__(self, verbose, dry_run, force) + self.__version = VERSION + self.__root = r"Software\Microsoft\VisualStudio" + # self.__macros = MACROS + self.__paths = [] + # target platform (.plat_name is consistent with 'bdist') + self.plat_name = None + self.__arch = None # deprecated name + self.initialized = False + + def initialize(self, plat_name=None): + # multi-init means we would need to check platform same each time... + assert not self.initialized, "don't init multiple times" + if plat_name is None: + plat_name = get_platform() + # sanity check for platforms to prevent obscure errors later. + ok_plats = 'win32', 'win-amd64', 'win-ia64' + if plat_name not in ok_plats: + raise PackagingPlatformError("--plat-name must be one of %s" % + (ok_plats,)) + + if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): + # Assume that the SDK set up everything alright; don't try to be + # smarter + self.cc = "cl.exe" + self.linker = "link.exe" + self.lib = "lib.exe" + self.rc = "rc.exe" + self.mc = "mc.exe" + else: + # On x86, 'vcvars32.bat amd64' creates an env that doesn't work; + # to cross compile, you use 'x86_amd64'. + # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross + # compile use 'x86' (ie, it runs the x86 compiler directly) + # No idea how itanium handles this, if at all. + if plat_name == get_platform() or plat_name == 'win32': + # native build or cross-compile to win32 + plat_spec = PLAT_TO_VCVARS[plat_name] + else: + # cross compile from win32 -> some 64bit + plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \ + PLAT_TO_VCVARS[plat_name] + + vc_env = query_vcvarsall(VERSION, plat_spec) + + # take care to only use strings in the environment. + self.__paths = vc_env['path'].encode('mbcs').split(os.pathsep) + os.environ['lib'] = vc_env['lib'].encode('mbcs') + os.environ['include'] = vc_env['include'].encode('mbcs') + + if len(self.__paths) == 0: + raise PackagingPlatformError("Python was built with %s, " + "and extensions need to be built with the same " + "version of the compiler, but it isn't installed." + % self.__product) + + self.cc = self.find_exe("cl.exe") + self.linker = self.find_exe("link.exe") + self.lib = self.find_exe("lib.exe") + self.rc = self.find_exe("rc.exe") # resource compiler + self.mc = self.find_exe("mc.exe") # message compiler + #self.set_path_env_var('lib') + #self.set_path_env_var('include') + + # extend the MSVC path with the current path + try: + for p in os.environ['path'].split(';'): + self.__paths.append(p) + except KeyError: + pass + self.__paths = normalize_and_reduce_paths(self.__paths) + os.environ['path'] = ";".join(self.__paths) + + self.preprocess_options = None + if self.__arch == "x86": + self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', + '/Z7', '/D_DEBUG'] + else: + # Win64 + self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', + '/Z7', '/D_DEBUG'] + + self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] + if self.__version >= 7: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG', '/pdb:None' + ] + self.ldflags_static = [ '/nologo'] + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, + source_filenames, + strip_dir=False, + output_dir=''): + # Copied from ccompiler.py, extended to return .res as 'object'-file + # for .rc input file + if output_dir is None: output_dir = '' + obj_names = [] + for src_name in source_filenames: + base, ext = os.path.splitext(src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError("Don't know how to compile %s" % src_name) + if strip_dir: + base = os.path.basename(base) + if ext in self._rc_extensions: + obj_names.append(os.path.join(output_dir, + base + self.res_extension)) + elif ext in self._mc_extensions: + obj_names.append(os.path.join(output_dir, + base + self.res_extension)) + else: + obj_names.append(os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=False, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + compile_info = self._setup_compile(output_dir, macros, include_dirs, + sources, depends, extra_postargs) + macros, objects, extra_postargs, pp_opts, build = compile_info + + compile_opts = extra_preargs or [] + compile_opts.append('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + + [output_opt] + [input_opt]) + except PackagingExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc] + + ['-h', h_dir, '-r', rc_dir] + [src]) + base, _ = os.path.splitext(os.path.basename(src)) + rc_file = os.path.join(rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc] + + ["/fo" + obj] + [rc_file]) + + except PackagingExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError("Don't know how to compile %s to %s" + % (src, obj)) + + output_opt = "/Fo" + obj + try: + self.spawn([self.cc] + compile_opts + pp_opts + + [input_opt, output_opt] + + extra_postargs) + except PackagingExecError as msg: + raise CompileError(msg) + + return objects + + + def create_static_lib(self, + objects, + output_libname, + output_dir=None, + debug=False, + target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + output_filename = self.library_filename(output_libname, + output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + self.spawn([self.lib] + lib_args) + except PackagingExecError as msg: + raise LibError(msg) + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + + def link(self, target_desc, objects, output_filename, output_dir=None, + libraries=None, library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=False, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + libraries, library_dirs, runtime_library_dirs = fixed_args + + if runtime_library_dirs: + self.warn("don't know what to do with 'runtime_library_dirs': " + + str(runtime_library_dirs)) + + lib_opts = gen_lib_options(self, + library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + if target_desc == CCompiler.EXECUTABLE: + if debug: + ldflags = self.ldflags_shared_debug[1:] + else: + ldflags = self.ldflags_shared[1:] + else: + if debug: + ldflags = self.ldflags_shared_debug + else: + ldflags = self.ldflags_shared + + export_opts = [] + for sym in (export_symbols or []): + export_opts.append("/EXPORT:" + sym) + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + build_temp = os.path.dirname(objects[0]) + if export_symbols is not None: + dll_name, dll_ext = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + build_temp, + self.library_filename(dll_name)) + ld_args.append('/IMPLIB:' + implib_file) + + # Embedded manifests are recommended - see MSDN article titled + # "How to: Embed a Manifest Inside a C/C++ Application" + # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) + # Ask the linker to generate the manifest in the temp dir, so + # we can embed it later. + temp_manifest = os.path.join( + build_temp, + os.path.basename(output_filename) + ".manifest") + ld_args.append('/MANIFESTFILE:' + temp_manifest) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + self.mkpath(os.path.dirname(output_filename)) + try: + self.spawn([self.linker] + ld_args) + except PackagingExecError as msg: + raise LinkError(msg) + + # embed the manifest + # XXX - this is somewhat fragile - if mt.exe fails, distutils + # will still consider the DLL up-to-date, but it will not have a + # manifest. Maybe we should link to a temp file? OTOH, that + # implies a build environment error that shouldn't go undetected. + if target_desc == CCompiler.EXECUTABLE: + mfid = 1 + else: + mfid = 2 + self._remove_visual_c_ref(temp_manifest) + out_arg = '-outputresource:%s;%s' % (output_filename, mfid) + try: + self.spawn(['mt.exe', '-nologo', '-manifest', + temp_manifest, out_arg]) + except PackagingExecError as msg: + raise LinkError(msg) + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + def _remove_visual_c_ref(self, manifest_file): + try: + # Remove references to the Visual C runtime, so they will + # fall through to the Visual C dependency of Python.exe. + # This way, when installed for a restricted user (e.g. + # runtimes are not in WinSxS folder, but in Python's own + # folder), the runtimes do not need to be in every folder + # with .pyd's. + with open(manifest_file) as manifest_f: + manifest_buf = manifest_f.read() + pattern = re.compile( + r"""|)""", + re.DOTALL) + manifest_buf = re.sub(pattern, "", manifest_buf) + pattern = "\s*" + manifest_buf = re.sub(pattern, "", manifest_buf) + with open(manifest_file, 'w') as manifest_f: + manifest_f.write(manifest_buf) + except IOError: + pass + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise PackagingPlatformError( + "don't know how to set runtime library search path for MSVC++") + + def library_option(self, lib): + return self.library_filename(lib) + + + def find_library_file(self, dirs, lib, debug=False): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename(name)) + if os.path.exists(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None + + # Helper methods for using the MSVC registry settings + + def find_exe(self, exe): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + for p in self.__paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + + # didn't find it; try existing path + for p in os.environ['Path'].split(';'): + fn = os.path.join(os.path.abspath(p),exe) + if os.path.isfile(fn): + return fn + + return exe diff --git a/Lib/packaging/compiler/msvccompiler.py b/Lib/packaging/compiler/msvccompiler.py new file mode 100644 index 0000000000..97f76bb99f --- /dev/null +++ b/Lib/packaging/compiler/msvccompiler.py @@ -0,0 +1,636 @@ +"""CCompiler implementation for old Microsoft Visual Studio compilers. + +For a compiler compatible with VS 2005 and 2008, use msvc9compiler. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) + + +import sys +import os + +from packaging.errors import (PackagingExecError, PackagingPlatformError, + CompileError, LibError, LinkError) +from packaging.compiler.ccompiler import CCompiler +from packaging.compiler import gen_lib_options +from packaging import logger + +_can_read_reg = False +try: + import winreg + + _can_read_reg = True + hkey_mod = winreg + + RegOpenKeyEx = winreg.OpenKeyEx + RegEnumKey = winreg.EnumKey + RegEnumValue = winreg.EnumValue + RegError = winreg.error + +except ImportError: + try: + import win32api + import win32con + _can_read_reg = True + hkey_mod = win32con + + RegOpenKeyEx = win32api.RegOpenKeyEx + RegEnumKey = win32api.RegEnumKey + RegEnumValue = win32api.RegEnumValue + RegError = win32api.error + + except ImportError: + logger.warning( + "can't read registry to find the necessary compiler setting;\n" + "make sure that Python modules _winreg, win32api or win32con " + "are installed.") + +if _can_read_reg: + HKEYS = (hkey_mod.HKEY_USERS, + hkey_mod.HKEY_CURRENT_USER, + hkey_mod.HKEY_LOCAL_MACHINE, + hkey_mod.HKEY_CLASSES_ROOT) + + +def read_keys(base, key): + """Return list of registry keys.""" + + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + L = [] + i = 0 + while True: + try: + k = RegEnumKey(handle, i) + except RegError: + break + L.append(k) + i = i + 1 + return L + + +def read_values(base, key): + """Return dict of registry keys and values. + + All names are converted to lowercase. + """ + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + d = {} + i = 0 + while True: + try: + name, value, type = RegEnumValue(handle, i) + except RegError: + break + name = name.lower() + d[convert_mbcs(name)] = convert_mbcs(value) + i = i + 1 + return d + + +def convert_mbcs(s): + enc = getattr(s, "encode", None) + if enc is not None: + try: + s = enc("mbcs") + except UnicodeError: + pass + return s + + +class MacroExpander: + + def __init__(self, version): + self.macros = {} + self.load_macros(version) + + def set_macro(self, macro, path, key): + for base in HKEYS: + d = read_values(base, path) + if d: + self.macros["$(%s)" % macro] = d[key] + break + + def load_macros(self, version): + vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version + self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir") + self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir") + net = r"Software\Microsoft\.NETFramework" + self.set_macro("FrameworkDir", net, "installroot") + try: + if version > 7.0: + self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") + else: + self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") + except KeyError: + raise PackagingPlatformError( +"""Python was built with Visual Studio 2003; extensions must be built with +a compiler than can generate compatible binaries. Visual Studio 2003 was +not found on this system. If you have Cygwin installed, you can try +compiling with MingW32, by passing "-c mingw32" to setup.py.""") +# XXX update this comment for setup.cfg + + p = r"Software\Microsoft\NET Framework Setup\Product" + for base in HKEYS: + try: + h = RegOpenKeyEx(base, p) + except RegError: + continue + key = RegEnumKey(h, 0) + d = read_values(base, r"%s\%s" % (p, key)) + self.macros["$(FrameworkVersion)"] = d["version"] + + def sub(self, s): + for k, v in self.macros.items(): + s = s.replace(k, v) + return s + + +def get_build_version(): + """Return the version of MSVC that was used to build Python. + + For Python 2.3 and up, the version number is included in + sys.version. For earlier versions, assume the compiler is MSVC 6. + """ + + prefix = "MSC v." + i = sys.version.find(prefix) + if i == -1: + return 6 + i = i + len(prefix) + s, rest = sys.version[i:].split(" ", 1) + majorVersion = int(s[:-2]) - 6 + minorVersion = int(s[2:3]) / 10.0 + # I don't think paths are affected by minor version in version 6 + if majorVersion == 6: + minorVersion = 0 + if majorVersion >= 6: + return majorVersion + minorVersion + # else we don't know what version of the compiler this is + return None + + +def get_build_architecture(): + """Return the processor architecture. + + Possible results are "Intel", "Itanium", or "AMD64". + """ + + prefix = " bit (" + i = sys.version.find(prefix) + if i == -1: + return "Intel" + j = sys.version.find(")", i) + return sys.version[i+len(prefix):j] + + +def normalize_and_reduce_paths(paths): + """Return a list of normalized paths with duplicates removed. + + The current order of paths is maintained. + """ + # Paths are normalized so things like: /a and /a/ aren't both preserved. + reduced_paths = [] + for p in paths: + np = os.path.normpath(p) + # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. + if np not in reduced_paths: + reduced_paths.append(np) + return reduced_paths + + +class MSVCCompiler(CCompiler): + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + name = 'msvc' + description = "Microsoft Visual C++" + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + def __init__(self, verbose=0, dry_run=False, force=False): + CCompiler.__init__(self, verbose, dry_run, force) + self.__version = get_build_version() + self.__arch = get_build_architecture() + if self.__arch == "Intel": + # x86 + if self.__version >= 7: + self.__root = r"Software\Microsoft\VisualStudio" + self.__macros = MacroExpander(self.__version) + else: + self.__root = r"Software\Microsoft\Devstudio" + self.__product = "Visual Studio version %s" % self.__version + else: + # Win64. Assume this was built with the platform SDK + self.__product = "Microsoft SDK compiler %s" % (self.__version + 6) + + self.initialized = False + + def initialize(self): + self.__paths = [] + if ("DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and + self.find_exe("cl.exe")): + # Assume that the SDK set up everything alright; don't try to be + # smarter + self.cc = "cl.exe" + self.linker = "link.exe" + self.lib = "lib.exe" + self.rc = "rc.exe" + self.mc = "mc.exe" + else: + self.__paths = self.get_msvc_paths("path") + + if len(self.__paths) == 0: + raise PackagingPlatformError("Python was built with %s " + "and extensions need to be built with the same " + "version of the compiler, but it isn't installed." % + self.__product) + + self.cc = self.find_exe("cl.exe") + self.linker = self.find_exe("link.exe") + self.lib = self.find_exe("lib.exe") + self.rc = self.find_exe("rc.exe") # resource compiler + self.mc = self.find_exe("mc.exe") # message compiler + self.set_path_env_var('lib') + self.set_path_env_var('include') + + # extend the MSVC path with the current path + try: + for p in os.environ['path'].split(';'): + self.__paths.append(p) + except KeyError: + pass + self.__paths = normalize_and_reduce_paths(self.__paths) + os.environ['path'] = ';'.join(self.__paths) + + self.preprocess_options = None + if self.__arch == "Intel": + self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GX', + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX', + '/Z7', '/D_DEBUG'] + else: + # Win64 + self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GS-', + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', + '/Z7', '/D_DEBUG'] + + self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] + if self.__version >= 7: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' + ] + else: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG' + ] + self.ldflags_static = [ '/nologo'] + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, source_filenames, strip_dir=False, output_dir=''): + # Copied from ccompiler.py, extended to return .res as 'object'-file + # for .rc input file + if output_dir is None: + output_dir = '' + obj_names = [] + for src_name in source_filenames: + base, ext = os.path.splitext(src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError("Don't know how to compile %s" % src_name) + if strip_dir: + base = os.path.basename(base) + if ext in self._rc_extensions: + obj_names.append(os.path.join(output_dir, + base + self.res_extension)) + elif ext in self._mc_extensions: + obj_names.append(os.path.join(output_dir, + base + self.res_extension)) + else: + obj_names.append(os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=False, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + macros, objects, extra_postargs, pp_opts, build = \ + self._setup_compile(output_dir, macros, include_dirs, sources, + depends, extra_postargs) + + compile_opts = extra_preargs or [] + compile_opts.append('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + + [output_opt] + [input_opt]) + except PackagingExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc] + + ['-h', h_dir, '-r', rc_dir] + [src]) + base, _ = os.path.splitext(os.path.basename(src)) + rc_file = os.path.join(rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc] + + ["/fo" + obj] + [rc_file]) + + except PackagingExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError( + "Don't know how to compile %s to %s" % + (src, obj)) + + output_opt = "/Fo" + obj + try: + self.spawn([self.cc] + compile_opts + pp_opts + + [input_opt, output_opt] + + extra_postargs) + except PackagingExecError as msg: + raise CompileError(msg) + + return objects + + def create_static_lib(self, objects, output_libname, output_dir=None, + debug=False, target_lang=None): + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + output_filename = \ + self.library_filename(output_libname, output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + self.spawn([self.lib] + lib_args) + except PackagingExecError as msg: + raise LibError(msg) + + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + def link(self, target_desc, objects, output_filename, output_dir=None, + libraries=None, library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=False, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + libraries, library_dirs, runtime_library_dirs = \ + self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) + + if runtime_library_dirs: + self.warn("don't know what to do with 'runtime_library_dirs': %s" + % (runtime_library_dirs,)) + + lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + + if target_desc == CCompiler.EXECUTABLE: + if debug: + ldflags = self.ldflags_shared_debug[1:] + else: + ldflags = self.ldflags_shared[1:] + else: + if debug: + ldflags = self.ldflags_shared_debug + else: + ldflags = self.ldflags_shared + + export_opts = [] + for sym in (export_symbols or []): + export_opts.append("/EXPORT:" + sym) + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + if export_symbols is not None: + dll_name, dll_ext = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + os.path.dirname(objects[0]), + self.library_filename(dll_name)) + ld_args.append('/IMPLIB:' + implib_file) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + self.mkpath(os.path.dirname(output_filename)) + try: + self.spawn([self.linker] + ld_args) + except PackagingExecError as msg: + raise LinkError(msg) + + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise PackagingPlatformError("don't know how to set runtime library search path for MSVC++") + + def library_option(self, lib): + return self.library_filename(lib) + + def find_library_file(self, dirs, lib, debug=False): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename(name)) + if os.path.exists(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None + + # Helper methods for using the MSVC registry settings + + def find_exe(self, exe): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + + for p in self.__paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + + # didn't find it; try existing path + for p in os.environ['Path'].split(';'): + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + + return exe + + def get_msvc_paths(self, path, platform='x86'): + """Get a list of devstudio directories (include, lib or path). + + Return a list of strings. The list will be empty if unable to + access the registry or appropriate registry keys not found. + """ + + if not _can_read_reg: + return [] + + path = path + " dirs" + if self.__version >= 7: + key = (r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" + % (self.__root, self.__version)) + else: + key = (r"%s\6.0\Build System\Components\Platforms" + r"\Win32 (%s)\Directories" % (self.__root, platform)) + + for base in HKEYS: + d = read_values(base, key) + if d: + if self.__version >= 7: + return self.__macros.sub(d[path]).split(";") + else: + return d[path].split(";") + # MSVC 6 seems to create the registry entries we need only when + # the GUI is run. + if self.__version == 6: + for base in HKEYS: + if read_values(base, r"%s\6.0" % self.__root) is not None: + self.warn("It seems you have Visual Studio 6 installed, " + "but the expected registry settings are not present.\n" + "You must at least run the Visual Studio GUI once " + "so that these entries are created.") + break + return [] + + def set_path_env_var(self, name): + """Set environment variable 'name' to an MSVC path type value. + + This is equivalent to a SET command prior to execution of spawned + commands. + """ + + if name == "lib": + p = self.get_msvc_paths("library") + else: + p = self.get_msvc_paths(name) + if p: + os.environ[name] = ';'.join(p) + + +if get_build_version() >= 8.0: + logger.debug("importing new compiler from distutils.msvc9compiler") + OldMSVCCompiler = MSVCCompiler + from packaging.compiler.msvc9compiler import MSVCCompiler + # get_build_architecture not really relevant now we support cross-compile + from packaging.compiler.msvc9compiler import MacroExpander diff --git a/Lib/packaging/compiler/unixccompiler.py b/Lib/packaging/compiler/unixccompiler.py new file mode 100644 index 0000000000..8c24c0f6e3 --- /dev/null +++ b/Lib/packaging/compiler/unixccompiler.py @@ -0,0 +1,339 @@ +"""CCompiler implementation for Unix compilers. + +This module contains the UnixCCompiler class, a subclass of CCompiler +that handles the "typical" Unix-style command-line C compiler: + * macros defined with -Dname[=value] + * macros undefined with -Uname + * include search directories specified with -Idir + * libraries specified with -lllib + * library search directories specified with -Ldir + * compile handled by 'cc' (or similar) executable with -c option: + compiles .c to .o + * link static library handled by 'ar' command (possibly with 'ranlib') + * link shared library handled by 'cc -shared' +""" + +import os, sys + +from packaging.util import newer +from packaging.compiler.ccompiler import CCompiler +from packaging.compiler import gen_preprocess_options, gen_lib_options +from packaging.errors import (PackagingExecError, CompileError, + LibError, LinkError) +from packaging import logger +import sysconfig + + +# XXX Things not currently handled: +# * optimization/debug/warning flags; we just use whatever's in Python's +# Makefile and live with it. Is this adequate? If not, we might +# have to have a bunch of subclasses GNUCCompiler, SGICCompiler, +# SunCCompiler, and I suspect down that road lies madness. +# * even if we don't know a warning flag from an optimization flag, +# we need some way for outsiders to feed preprocessor/compiler/linker +# flags in to us -- eg. a sysadmin might want to mandate certain flags +# via a site config file, or a user might want to set something for +# compiling this module distribution only via the setup.py command +# line, whatever. As long as these options come from something on the +# current system, they can be as system-dependent as they like, and we +# should just happily stuff them into the preprocessor/compiler/linker +# options and carry on. + +def _darwin_compiler_fixup(compiler_so, cc_args): + """ + This function will strip '-isysroot PATH' and '-arch ARCH' from the + compile flags if the user has specified one them in extra_compile_flags. + + This is needed because '-arch ARCH' adds another architecture to the + build, without a way to remove an architecture. Furthermore GCC will + barf if multiple '-isysroot' arguments are present. + """ + stripArch = stripSysroot = False + + compiler_so = list(compiler_so) + kernel_version = os.uname()[2] # 8.4.3 + major_version = int(kernel_version.split('.')[0]) + + if major_version < 8: + # OSX before 10.4.0, these don't support -arch and -isysroot at + # all. + stripArch = stripSysroot = True + else: + stripArch = '-arch' in cc_args + stripSysroot = '-isysroot' in cc_args + + if stripArch or 'ARCHFLAGS' in os.environ: + while True: + try: + index = compiler_so.index('-arch') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + break + + if 'ARCHFLAGS' in os.environ and not stripArch: + # User specified different -arch flags in the environ, + # see also the sysconfig + compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() + + if stripSysroot: + try: + index = compiler_so.index('-isysroot') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + pass + + # Check if the SDK that is used during compilation actually exists, + # the universal build requires the usage of a universal SDK and not all + # users have that installed by default. + sysroot = None + if '-isysroot' in cc_args: + idx = cc_args.index('-isysroot') + sysroot = cc_args[idx+1] + elif '-isysroot' in compiler_so: + idx = compiler_so.index('-isysroot') + sysroot = compiler_so[idx+1] + + if sysroot and not os.path.isdir(sysroot): + logger.warning( + "compiling with an SDK that doesn't seem to exist: %r;\n" + "please check your Xcode installation", sysroot) + + return compiler_so + +class UnixCCompiler(CCompiler): + + name = 'unix' + description = 'Standard UNIX-style compiler' + + # These are used by CCompiler in two places: the constructor sets + # instance attributes 'preprocessor', 'compiler', etc. from them, and + # 'set_executable()' allows any of these to be set. The defaults here + # are pretty generic; they will probably have to be set by an outsider + # (eg. using information discovered by the sysconfig about building + # Python extensions). + executables = {'preprocessor' : None, + 'compiler' : ["cc"], + 'compiler_so' : ["cc"], + 'compiler_cxx' : ["cc"], + 'linker_so' : ["cc", "-shared"], + 'linker_exe' : ["cc"], + 'archiver' : ["ar", "-cr"], + 'ranlib' : None, + } + + if sys.platform[:6] == "darwin": + executables['ranlib'] = ["ranlib"] + + # Needed for the filename generation methods provided by the base + # class, CCompiler. NB. whoever instantiates/uses a particular + # UnixCCompiler instance should set 'shared_lib_ext' -- we set a + # reasonable common default here, but it's not necessarily used on all + # Unices! + + src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"] + obj_extension = ".o" + static_lib_extension = ".a" + shared_lib_extension = ".so" + dylib_lib_extension = ".dylib" + static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" + if sys.platform == "cygwin": + exe_extension = ".exe" + + def preprocess(self, source, + output_file=None, macros=None, include_dirs=None, + extra_preargs=None, extra_postargs=None): + ignore, macros, include_dirs = \ + self._fix_compile_args(None, macros, include_dirs) + pp_opts = gen_preprocess_options(macros, include_dirs) + pp_args = self.preprocessor + pp_opts + if output_file: + pp_args.extend(('-o', output_file)) + if extra_preargs: + pp_args[:0] = extra_preargs + if extra_postargs: + pp_args.extend(extra_postargs) + pp_args.append(source) + + # We need to preprocess: either we're being forced to, or we're + # generating output to stdout, or there's a target output file and + # the source file is newer than the target (or the target doesn't + # exist). + if self.force or output_file is None or newer(source, output_file): + if output_file: + self.mkpath(os.path.dirname(output_file)) + try: + self.spawn(pp_args) + except PackagingExecError as msg: + raise CompileError(msg) + + def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): + compiler_so = self.compiler_so + if sys.platform == 'darwin': + compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs) + try: + self.spawn(compiler_so + cc_args + [src, '-o', obj] + + extra_postargs) + except PackagingExecError as msg: + raise CompileError(msg) + + def create_static_lib(self, objects, output_libname, + output_dir=None, debug=False, target_lang=None): + objects, output_dir = self._fix_object_args(objects, output_dir) + + output_filename = \ + self.library_filename(output_libname, output_dir=output_dir) + + if self._need_link(objects, output_filename): + self.mkpath(os.path.dirname(output_filename)) + self.spawn(self.archiver + + [output_filename] + + objects + self.objects) + + # Not many Unices required ranlib anymore -- SunOS 4.x is, I + # think the only major Unix that does. Maybe we need some + # platform intelligence here to skip ranlib if it's not + # needed -- or maybe Python's configure script took care of + # it for us, hence the check for leading colon. + if self.ranlib: + try: + self.spawn(self.ranlib + [output_filename]) + except PackagingExecError as msg: + raise LibError(msg) + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + def link(self, target_desc, objects, + output_filename, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=False, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + objects, output_dir = self._fix_object_args(objects, output_dir) + libraries, library_dirs, runtime_library_dirs = \ + self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) + + lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, + libraries) + if type(output_dir) not in (str, type(None)): + raise TypeError("'output_dir' must be a string or None") + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + ld_args = (objects + self.objects + + lib_opts + ['-o', output_filename]) + if debug: + ld_args[:0] = ['-g'] + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + self.mkpath(os.path.dirname(output_filename)) + try: + if target_desc == CCompiler.EXECUTABLE: + linker = self.linker_exe[:] + else: + linker = self.linker_so[:] + if target_lang == "c++" and self.compiler_cxx: + # skip over environment variable settings if /usr/bin/env + # is used to set up the linker's environment. + # This is needed on OSX. Note: this assumes that the + # normal and C++ compiler have the same environment + # settings. + i = 0 + if os.path.basename(linker[0]) == "env": + i = 1 + while '=' in linker[i]: + i = i + 1 + + linker[i] = self.compiler_cxx[i] + + if sys.platform == 'darwin': + linker = _darwin_compiler_fixup(linker, ld_args) + + self.spawn(linker + ld_args) + except PackagingExecError as msg: + raise LinkError(msg) + else: + logger.debug("skipping %s (up-to-date)", output_filename) + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "-L" + dir + + def _is_gcc(self, compiler_name): + return "gcc" in compiler_name or "g++" in compiler_name + + def runtime_library_dir_option(self, dir): + # XXX Hackish, at the very least. See Python bug #445902: + # http://sourceforge.net/tracker/index.php + # ?func=detail&aid=445902&group_id=5470&atid=105470 + # Linkers on different platforms need different options to + # specify that directories need to be added to the list of + # directories searched for dependencies when a dynamic library + # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to + # be told to pass the -R option through to the linker, whereas + # other compilers and gcc on other systems just know this. + # Other compilers may need something slightly different. At + # this time, there's no way to determine this information from + # the configuration data stored in the Python installation, so + # we use this hack. + + compiler = os.path.basename(sysconfig.get_config_var("CC")) + if sys.platform[:6] == "darwin": + # MacOSX's linker doesn't understand the -R flag at all + return "-L" + dir + elif sys.platform[:5] == "hp-ux": + if self._is_gcc(compiler): + return ["-Wl,+s", "-L" + dir] + return ["+s", "-L" + dir] + elif sys.platform[:7] == "irix646" or sys.platform[:6] == "osf1V5": + return ["-rpath", dir] + elif self._is_gcc(compiler): + # gcc on non-GNU systems does not need -Wl, but can + # use it anyway. Since distutils has always passed in + # -Wl whenever gcc was used in the past it is probably + # safest to keep doing so. + if sysconfig.get_config_var("GNULD") == "yes": + # GNU ld needs an extra option to get a RUNPATH + # instead of just an RPATH. + return "-Wl,--enable-new-dtags,-R" + dir + else: + return "-Wl,-R" + dir + elif sys.platform[:3] == "aix": + return "-blibpath:" + dir + else: + # No idea how --enable-new-dtags would be passed on to + # ld if this system was using GNU ld. Don't know if a + # system like this even exists. + return "-R" + dir + + def library_option(self, lib): + return "-l" + lib + + def find_library_file(self, dirs, lib, debug=False): + shared_f = self.library_filename(lib, lib_type='shared') + dylib_f = self.library_filename(lib, lib_type='dylib') + static_f = self.library_filename(lib, lib_type='static') + + for dir in dirs: + shared = os.path.join(dir, shared_f) + dylib = os.path.join(dir, dylib_f) + static = os.path.join(dir, static_f) + # We're second-guessing the linker here, with not much hard + # data to go on: GCC seems to prefer the shared library, so I'm + # assuming that *all* Unix C compilers do. And of course I'm + # ignoring even GCC's "-static" option. So sue me. + if os.path.exists(dylib): + return dylib + elif os.path.exists(shared): + return shared + elif os.path.exists(static): + return static + + # Oops, didn't find it in *any* of 'dirs' + return None diff --git a/Lib/packaging/config.py b/Lib/packaging/config.py new file mode 100644 index 0000000000..9239f4a83b --- /dev/null +++ b/Lib/packaging/config.py @@ -0,0 +1,357 @@ +"""Utilities to find and read config files used by packaging.""" + +import os +import sys +import logging + +from shlex import split +from configparser import RawConfigParser +from packaging import logger +from packaging.errors import PackagingOptionError +from packaging.compiler.extension import Extension +from packaging.util import check_environ, iglob, resolve_name, strtobool +from packaging.compiler import set_compiler +from packaging.command import set_command +from packaging.markers import interpret + + +def _pop_values(values_dct, key): + """Remove values from the dictionary and convert them as a list""" + vals_str = values_dct.pop(key, '') + if not vals_str: + return + fields = [] + for field in vals_str.split(os.linesep): + tmp_vals = field.split('--') + if len(tmp_vals) == 2 and not interpret(tmp_vals[1]): + continue + fields.append(tmp_vals[0]) + # Get bash options like `gcc -print-file-name=libgcc.a` XXX bash options? + vals = split(' '.join(fields)) + if vals: + return vals + + +def _rel_path(base, path): + assert path.startswith(base) + return path[len(base):].lstrip('/') + + +def get_resources_dests(resources_root, rules): + """Find destinations for resources files""" + destinations = {} + for base, suffix, dest in rules: + prefix = os.path.join(resources_root, base) + for abs_base in iglob(prefix): + abs_glob = os.path.join(abs_base, suffix) + for abs_path in iglob(abs_glob): + resource_file = _rel_path(resources_root, abs_path) + if dest is None: # remove the entry if it was here + destinations.pop(resource_file, None) + else: + rel_path = _rel_path(abs_base, abs_path) + destinations[resource_file] = os.path.join(dest, rel_path) + return destinations + + +class Config: + """Reads configuration files and work with the Distribution instance + """ + def __init__(self, dist): + self.dist = dist + self.setup_hook = None + + def run_hook(self, config): + if self.setup_hook is None: + return + # the hook gets only the config + self.setup_hook(config) + + def find_config_files(self): + """Find as many configuration files as should be processed for this + platform, and return a list of filenames in the order in which they + should be parsed. The filenames returned are guaranteed to exist + (modulo nasty race conditions). + + There are three possible config files: packaging.cfg in the + Packaging installation directory (ie. where the top-level + Packaging __inst__.py file lives), a file in the user's home + directory named .pydistutils.cfg on Unix and pydistutils.cfg + on Windows/Mac; and setup.cfg in the current directory. + + The file in the user's home directory can be disabled with the + --no-user-cfg option. + """ + files = [] + check_environ() + + # Where to look for the system-wide Packaging config file + sys_dir = os.path.dirname(sys.modules['packaging'].__file__) + + # Look for the system config file + sys_file = os.path.join(sys_dir, "packaging.cfg") + if os.path.isfile(sys_file): + files.append(sys_file) + + # What to call the per-user config file + if os.name == 'posix': + user_filename = ".pydistutils.cfg" + else: + user_filename = "pydistutils.cfg" + + # And look for the user config file + if self.dist.want_user_cfg: + user_file = os.path.join(os.path.expanduser('~'), user_filename) + if os.path.isfile(user_file): + files.append(user_file) + + # All platforms support local setup.cfg + local_file = "setup.cfg" + if os.path.isfile(local_file): + files.append(local_file) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug("using config files: %s", ', '.join(files)) + return files + + def _convert_metadata(self, name, value): + # converts a value found in setup.cfg into a valid metadata + # XXX + return value + + def _multiline(self, value): + value = [v for v in + [v.strip() for v in value.split('\n')] + if v != ''] + return value + + def _read_setup_cfg(self, parser, cfg_filename): + cfg_directory = os.path.dirname(os.path.abspath(cfg_filename)) + content = {} + for section in parser.sections(): + content[section] = dict(parser.items(section)) + + # global:setup_hook is called *first* + if 'global' in content: + if 'setup_hook' in content['global']: + setup_hook = content['global']['setup_hook'] + try: + self.setup_hook = resolve_name(setup_hook) + except ImportError as e: + logger.warning('could not import setup_hook: %s', + e.args[0]) + else: + self.run_hook(content) + + metadata = self.dist.metadata + + # setting the metadata values + if 'metadata' in content: + for key, value in content['metadata'].items(): + key = key.replace('_', '-') + if metadata.is_multi_field(key): + value = self._multiline(value) + + if key == 'project-url': + value = [(label.strip(), url.strip()) + for label, url in + [v.split(',') for v in value]] + + if key == 'description-file': + if 'description' in content['metadata']: + msg = ("description and description-file' are " + "mutually exclusive") + raise PackagingOptionError(msg) + + if isinstance(value, list): + filenames = value + else: + filenames = value.split() + + # concatenate each files + value = '' + for filename in filenames: + # will raise if file not found + with open(filename) as description_file: + value += description_file.read().strip() + '\n' + # add filename as a required file + if filename not in metadata.requires_files: + metadata.requires_files.append(filename) + value = value.strip() + key = 'description' + + if metadata.is_metadata_field(key): + metadata[key] = self._convert_metadata(key, value) + + if 'files' in content: + files = content['files'] + self.dist.package_dir = files.pop('packages_root', None) + + files = dict((key, self._multiline(value)) for key, value in + files.items()) + + self.dist.packages = [] + + packages = files.get('packages', []) + if isinstance(packages, str): + packages = [packages] + + for package in packages: + if ':' in package: + dir_, package = package.split(':') + self.dist.package_dir[package] = dir_ + self.dist.packages.append(package) + + self.dist.py_modules = files.get('modules', []) + if isinstance(self.dist.py_modules, str): + self.dist.py_modules = [self.dist.py_modules] + self.dist.scripts = files.get('scripts', []) + if isinstance(self.dist.scripts, str): + self.dist.scripts = [self.dist.scripts] + + self.dist.package_data = {} + for data in files.get('package_data', []): + data = data.split('=') + if len(data) != 2: + continue # XXX error should never pass silently + key, value = data + self.dist.package_data[key.strip()] = value.strip() + + self.dist.data_files = [] + for data in files.get('data_files', []): + data = data.split('=') + if len(data) != 2: + continue + key, value = data + values = [v.strip() for v in value.split(',')] + self.dist.data_files.append((key, values)) + + # manifest template + self.dist.extra_files = files.get('extra_files', []) + + resources = [] + for rule in files.get('resources', []): + glob, destination = rule.split('=', 1) + rich_glob = glob.strip().split(' ', 1) + if len(rich_glob) == 2: + prefix, suffix = rich_glob + else: + assert len(rich_glob) == 1 + prefix = '' + suffix = glob + if destination == '': + destination = None + resources.append( + (prefix.strip(), suffix.strip(), destination.strip())) + self.dist.data_files = get_resources_dests( + cfg_directory, resources) + + ext_modules = self.dist.ext_modules + for section_key in content: + labels = section_key.split('=') + if len(labels) == 2 and labels[0] == 'extension': + # labels[1] not used from now but should be implemented + # for extension build dependency + values_dct = content[section_key] + ext_modules.append(Extension( + values_dct.pop('name'), + _pop_values(values_dct, 'sources'), + _pop_values(values_dct, 'include_dirs'), + _pop_values(values_dct, 'define_macros'), + _pop_values(values_dct, 'undef_macros'), + _pop_values(values_dct, 'library_dirs'), + _pop_values(values_dct, 'libraries'), + _pop_values(values_dct, 'runtime_library_dirs'), + _pop_values(values_dct, 'extra_objects'), + _pop_values(values_dct, 'extra_compile_args'), + _pop_values(values_dct, 'extra_link_args'), + _pop_values(values_dct, 'export_symbols'), + _pop_values(values_dct, 'swig_opts'), + _pop_values(values_dct, 'depends'), + values_dct.pop('language', None), + values_dct.pop('optional', None), + **values_dct)) + + def parse_config_files(self, filenames=None): + if filenames is None: + filenames = self.find_config_files() + + logger.debug("Distribution.parse_config_files():") + + parser = RawConfigParser() + + for filename in filenames: + logger.debug(" reading %s", filename) + parser.read(filename) + + if os.path.split(filename)[-1] == 'setup.cfg': + self._read_setup_cfg(parser, filename) + + for section in parser.sections(): + if section == 'global': + if parser.has_option('global', 'compilers'): + self._load_compilers(parser.get('global', 'compilers')) + + if parser.has_option('global', 'commands'): + self._load_commands(parser.get('global', 'commands')) + + options = parser.options(section) + opt_dict = self.dist.get_option_dict(section) + + for opt in options: + if opt == '__name__': + continue + val = parser.get(section, opt) + opt = opt.replace('-', '_') + + if opt == 'sub_commands': + val = self._multiline(val) + if isinstance(val, str): + val = [val] + + # Hooks use a suffix system to prevent being overriden + # by a config file processed later (i.e. a hook set in + # the user config file cannot be replaced by a hook + # set in a project config file, unless they have the + # same suffix). + if (opt.startswith("pre_hook.") or + opt.startswith("post_hook.")): + hook_type, alias = opt.split(".") + hook_dict = opt_dict.setdefault( + hook_type, (filename, {}))[1] + hook_dict[alias] = val + else: + opt_dict[opt] = filename, val + + # Make the RawConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + # If there was a "global" section in the config file, use it + # to set Distribution options. + if 'global' in self.dist.command_options: + for opt, (src, val) in self.dist.command_options['global'].items(): + alias = self.dist.negative_opt.get(opt) + try: + if alias: + setattr(self.dist, alias, not strtobool(val)) + elif opt == 'dry_run': # FIXME ugh! + setattr(self.dist, opt, strtobool(val)) + else: + setattr(self.dist, opt, val) + except ValueError as msg: + raise PackagingOptionError(msg) + + def _load_compilers(self, compilers): + compilers = self._multiline(compilers) + if isinstance(compilers, str): + compilers = [compilers] + for compiler in compilers: + set_compiler(compiler.strip()) + + def _load_commands(self, commands): + commands = self._multiline(commands) + if isinstance(commands, str): + commands = [commands] + for command in commands: + set_command(command.strip()) diff --git a/Lib/packaging/create.py b/Lib/packaging/create.py new file mode 100644 index 0000000000..837d0b6b88 --- /dev/null +++ b/Lib/packaging/create.py @@ -0,0 +1,693 @@ +#!/usr/bin/env python +"""Interactive helper used to create a setup.cfg file. + +This script will generate a packaging configuration file by looking at +the current directory and asking the user questions. It is intended to +be called as + + pysetup create + +or + + python3.3 -m packaging.create +""" + +# Original code by Sean Reifschneider + +# Original TODO list: +# Look for a license file and automatically add the category. +# When a .c file is found during the walk, can we add it as an extension? +# Ask if there is a maintainer different that the author +# Ask for the platform (can we detect this via "import win32" or something?) +# Ask for the dependencies. +# Ask for the Requires-Dist +# Ask for the Provides-Dist +# Ask for a description +# Detect scripts (not sure how. #! outside of package?) + +import os +import imp +import sys +import glob +import re +import shutil +import sysconfig +from configparser import RawConfigParser +from textwrap import dedent +from hashlib import md5 +from functools import cmp_to_key +# importing this with an underscore as it should be replaced by the +# dict form or another structures for all purposes +from packaging._trove import all_classifiers as _CLASSIFIERS_LIST +from packaging.version import is_valid_version + +_FILENAME = 'setup.cfg' +_DEFAULT_CFG = '.pypkgcreate' + +_helptext = { + 'name': ''' +The name of the program to be packaged, usually a single word composed +of lower-case characters such as "python", "sqlalchemy", or "CherryPy". +''', + 'version': ''' +Version number of the software, typically 2 or 3 numbers separated by dots +such as "1.00", "0.6", or "3.02.01". "0.1.0" is recommended for initial +development. +''', + 'summary': ''' +A one-line summary of what this project is or does, typically a sentence 80 +characters or less in length. +''', + 'author': ''' +The full name of the author (typically you). +''', + 'author_email': ''' +E-mail address of the project author (typically you). +''', + 'do_classifier': ''' +Trove classifiers are optional identifiers that allow you to specify the +intended audience by saying things like "Beta software with a text UI +for Linux under the PSF license. However, this can be a somewhat involved +process. +''', + 'packages': ''' +You can provide a package name contained in your project. +''', + 'modules': ''' +You can provide a python module contained in your project. +''', + 'extra_files': ''' +You can provide extra files/dirs contained in your project. +It has to follow the template syntax. XXX add help here. +''', + + 'home_page': ''' +The home page for the project, typically starting with "http://". +''', + 'trove_license': ''' +Optionally you can specify a license. Type a string that identifies a common +license, and then you can select a list of license specifiers. +''', + 'trove_generic': ''' +Optionally, you can set other trove identifiers for things such as the +human language, programming language, user interface, etc... +''', + 'setup.py found': ''' +The setup.py script will be executed to retrieve the metadata. +A wizard will be run if you answer "n", +''', +} + +PROJECT_MATURITY = ['Development Status :: 1 - Planning', + 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', + 'Development Status :: 6 - Mature', + 'Development Status :: 7 - Inactive'] + +# XXX everything needs docstrings and tests (both low-level tests of various +# methods and functional tests of running the script) + + +def load_setup(): + """run the setup script (i.e the setup.py file) + + This function load the setup file in all cases (even if it have already + been loaded before, because we are monkey patching its setup function with + a particular one""" + with open("setup.py") as f: + imp.load_module("setup", f, "setup.py", (".py", "r", imp.PY_SOURCE)) + + +def ask_yn(question, default=None, helptext=None): + question += ' (y/n)' + while True: + answer = ask(question, default, helptext, required=True) + if answer and answer[0].lower() in 'yn': + return answer[0].lower() + + print('\nERROR: You must select "Y" or "N".\n') + + +def ask(question, default=None, helptext=None, required=True, + lengthy=False, multiline=False): + prompt = '%s: ' % (question,) + if default: + prompt = '%s [%s]: ' % (question, default) + if default and len(question) + len(default) > 70: + prompt = '%s\n [%s]: ' % (question, default) + if lengthy or multiline: + prompt += '\n > ' + + if not helptext: + helptext = 'No additional help available.' + + helptext = helptext.strip("\n") + + while True: + sys.stdout.write(prompt) + sys.stdout.flush() + + line = sys.stdin.readline().strip() + if line == '?': + print('=' * 70) + print(helptext) + print('=' * 70) + continue + if default and not line: + return default + if not line and required: + print('*' * 70) + print('This value cannot be empty.') + print('===========================') + if helptext: + print(helptext) + print('*' * 70) + continue + return line + + +def convert_yn_to_bool(yn, yes=True, no=False): + """Convert a y/yes or n/no to a boolean value.""" + if yn.lower().startswith('y'): + return yes + else: + return no + + +def _build_classifiers_dict(classifiers): + d = {} + for key in classifiers: + subDict = d + for subkey in key.split(' :: '): + if not subkey in subDict: + subDict[subkey] = {} + subDict = subDict[subkey] + return d + +CLASSIFIERS = _build_classifiers_dict(_CLASSIFIERS_LIST) + + +def _build_licences(classifiers): + res = [] + for index, item in enumerate(classifiers): + if not item.startswith('License :: '): + continue + res.append((index, item.split(' :: ')[-1].lower())) + return res + +LICENCES = _build_licences(_CLASSIFIERS_LIST) + + +class MainProgram: + """Make a project setup configuration file (setup.cfg).""" + + def __init__(self): + self.configparser = None + self.classifiers = set() + self.data = {'name': '', + 'version': '1.0.0', + 'classifier': self.classifiers, + 'packages': [], + 'modules': [], + 'platform': [], + 'resources': [], + 'extra_files': [], + 'scripts': [], + } + self._load_defaults() + + def __call__(self): + setupcfg_defined = False + if self.has_setup_py() and self._prompt_user_for_conversion(): + setupcfg_defined = self.convert_py_to_cfg() + if not setupcfg_defined: + self.define_cfg_values() + self._write_cfg() + + def has_setup_py(self): + """Test for the existance of a setup.py file.""" + return os.path.exists('setup.py') + + def define_cfg_values(self): + self.inspect() + self.query_user() + + def _lookup_option(self, key): + if not self.configparser.has_option('DEFAULT', key): + return None + return self.configparser.get('DEFAULT', key) + + def _load_defaults(self): + # Load default values from a user configuration file + self.configparser = RawConfigParser() + # TODO replace with section in distutils config file + default_cfg = os.path.expanduser(os.path.join('~', _DEFAULT_CFG)) + self.configparser.read(default_cfg) + self.data['author'] = self._lookup_option('author') + self.data['author_email'] = self._lookup_option('author_email') + + def _prompt_user_for_conversion(self): + # Prompt the user about whether they would like to use the setup.py + # conversion utility to generate a setup.cfg or generate the setup.cfg + # from scratch + answer = ask_yn(('A legacy setup.py has been found.\n' + 'Would you like to convert it to a setup.cfg?'), + default="y", + helptext=_helptext['setup.py found']) + return convert_yn_to_bool(answer) + + def _dotted_packages(self, data): + packages = sorted(data) + modified_pkgs = [] + for pkg in packages: + pkg = pkg.lstrip('./') + pkg = pkg.replace('/', '.') + modified_pkgs.append(pkg) + return modified_pkgs + + def _write_cfg(self): + if os.path.exists(_FILENAME): + if os.path.exists('%s.old' % _FILENAME): + print("ERROR: %(name)s.old backup exists, please check that " + "current %(name)s is correct and remove %(name)s.old" % + {'name': _FILENAME}) + return + shutil.move(_FILENAME, '%s.old' % _FILENAME) + + with open(_FILENAME, 'w') as fp: + fp.write('[metadata]\n') + # simple string entries + for name in ('name', 'version', 'summary', 'download_url'): + fp.write('%s = %s\n' % (name, self.data.get(name, 'UNKNOWN'))) + # optional string entries + if 'keywords' in self.data and self.data['keywords']: + fp.write('keywords = %s\n' % ' '.join(self.data['keywords'])) + for name in ('home_page', 'author', 'author_email', + 'maintainer', 'maintainer_email', 'description-file'): + if name in self.data and self.data[name]: + fp.write('%s = %s\n' % (name, self.data[name])) + if 'description' in self.data: + fp.write( + 'description = %s\n' + % '\n |'.join(self.data['description'].split('\n'))) + # multiple use string entries + for name in ('platform', 'supported-platform', 'classifier', + 'requires-dist', 'provides-dist', 'obsoletes-dist', + 'requires-external'): + if not(name in self.data and self.data[name]): + continue + fp.write('%s = ' % name) + fp.write(''.join(' %s\n' % val + for val in self.data[name]).lstrip()) + fp.write('\n[files]\n') + for name in ('packages', 'modules', 'scripts', + 'package_data', 'extra_files'): + if not(name in self.data and self.data[name]): + continue + fp.write('%s = %s\n' + % (name, '\n '.join(self.data[name]).strip())) + fp.write('\nresources =\n') + for src, dest in self.data['resources']: + fp.write(' %s = %s\n' % (src, dest)) + fp.write('\n') + + os.chmod(_FILENAME, 0o644) + print('Wrote "%s".' % _FILENAME) + + def convert_py_to_cfg(self): + """Generate a setup.cfg from an existing setup.py. + + It only exports the distutils metadata (setuptools specific metadata + is not currently supported). + """ + data = self.data + + def setup_mock(**attrs): + """Mock the setup(**attrs) in order to retrieve metadata.""" + # use the distutils v1 processings to correctly parse metadata. + #XXX we could also use the setuptools distibution ??? + from distutils.dist import Distribution + dist = Distribution(attrs) + dist.parse_config_files() + + # 1. retrieve metadata fields that are quite similar in + # PEP 314 and PEP 345 + labels = (('name',) * 2, + ('version',) * 2, + ('author',) * 2, + ('author_email',) * 2, + ('maintainer',) * 2, + ('maintainer_email',) * 2, + ('description', 'summary'), + ('long_description', 'description'), + ('url', 'home_page'), + ('platforms', 'platform'), + # backport only for 2.5+ + ('provides', 'provides-dist'), + ('obsoletes', 'obsoletes-dist'), + ('requires', 'requires-dist')) + + get = lambda lab: getattr(dist.metadata, lab.replace('-', '_')) + data.update((new, get(old)) for old, new in labels if get(old)) + + # 2. retrieve data that requires special processing + data['classifier'].update(dist.get_classifiers() or []) + data['scripts'].extend(dist.scripts or []) + data['packages'].extend(dist.packages or []) + data['modules'].extend(dist.py_modules or []) + # 2.1 data_files -> resources + if dist.data_files: + if len(dist.data_files) < 2 or \ + isinstance(dist.data_files[1], str): + dist.data_files = [('', dist.data_files)] + # add tokens in the destination paths + vars = {'distribution.name': data['name']} + path_tokens = list(sysconfig.get_paths(vars=vars).items()) + + def length_comparison(x, y): + len_x = len(x[1]) + len_y = len(y[1]) + if len_x == len_y: + return 0 + elif len_x < len_y: + return -1 + else: + return 1 + + # sort tokens to use the longest one first + path_tokens.sort(key=cmp_to_key(length_comparison)) + for dest, srcs in (dist.data_files or []): + dest = os.path.join(sys.prefix, dest) + for tok, path in path_tokens: + if dest.startswith(path): + dest = ('{%s}' % tok) + dest[len(path):] + files = [('/ '.join(src.rsplit('/', 1)), dest) + for src in srcs] + data['resources'].extend(files) + continue + # 2.2 package_data -> extra_files + package_dirs = dist.package_dir or {} + for package, extras in iter(dist.package_data.items()) or []: + package_dir = package_dirs.get(package, package) + files = [os.path.join(package_dir, f) for f in extras] + data['extra_files'].extend(files) + + # Use README file if its content is the desciption + if "description" in data: + ref = md5(re.sub('\s', '', + self.data['description']).lower().encode()) + ref = ref.digest() + for readme in glob.glob('README*'): + with open(readme) as fp: + contents = fp.read() + val = md5(re.sub('\s', '', + contents.lower()).encode()).digest() + if val == ref: + del data['description'] + data['description-file'] = readme + break + + # apply monkey patch to distutils (v1) and setuptools (if needed) + # (abort the feature if distutils v1 has been killed) + try: + from distutils import core + core.setup # make sure it's not d2 maskerading as d1 + except (ImportError, AttributeError): + return + saved_setups = [(core, core.setup)] + core.setup = setup_mock + try: + import setuptools + except ImportError: + pass + else: + saved_setups.append((setuptools, setuptools.setup)) + setuptools.setup = setup_mock + # get metadata by executing the setup.py with the patched setup(...) + success = False # for python < 2.4 + try: + load_setup() + success = True + finally: # revert monkey patches + for patched_module, original_setup in saved_setups: + patched_module.setup = original_setup + if not self.data: + raise ValueError('Unable to load metadata from setup.py') + return success + + def inspect_file(self, path): + with open(path, 'r') as fp: + for _ in range(10): + line = fp.readline() + m = re.match(r'^#!.*python((?P\d)(\.\d+)?)?$', line) + if m: + if m.group('major') == '3': + self.classifiers.add( + 'Programming Language :: Python :: 3') + else: + self.classifiers.add( + 'Programming Language :: Python :: 2') + + def inspect(self): + """Inspect the current working diretory for a name and version. + + This information is harvested in where the directory is named + like [name]-[version]. + """ + dir_name = os.path.basename(os.getcwd()) + self.data['name'] = dir_name + match = re.match(r'(.*)-(\d.+)', dir_name) + if match: + self.data['name'] = match.group(1) + self.data['version'] = match.group(2) + # TODO Needs tested! + if not is_valid_version(self.data['version']): + msg = "Invalid version discovered: %s" % self.data['version'] + raise RuntimeError(msg) + + def query_user(self): + self.data['name'] = ask('Project name', self.data['name'], + _helptext['name']) + + self.data['version'] = ask('Current version number', + self.data.get('version'), _helptext['version']) + self.data['summary'] = ask('Package summary', + self.data.get('summary'), _helptext['summary'], + lengthy=True) + self.data['author'] = ask('Author name', + self.data.get('author'), _helptext['author']) + self.data['author_email'] = ask('Author e-mail address', + self.data.get('author_email'), _helptext['author_email']) + self.data['home_page'] = ask('Project Home Page', + self.data.get('home_page'), _helptext['home_page'], + required=False) + + if ask_yn('Do you want me to automatically build the file list ' + 'with everything I can find in the current directory ? ' + 'If you say no, you will have to define them manually.') == 'y': + self._find_files() + else: + while ask_yn('Do you want to add a single module ?' + ' (you will be able to add full packages next)', + helptext=_helptext['modules']) == 'y': + self._set_multi('Module name', 'modules') + + while ask_yn('Do you want to add a package ?', + helptext=_helptext['packages']) == 'y': + self._set_multi('Package name', 'packages') + + while ask_yn('Do you want to add an extra file ?', + helptext=_helptext['extra_files']) == 'y': + self._set_multi('Extra file/dir name', 'extra_files') + + if ask_yn('Do you want to set Trove classifiers?', + helptext=_helptext['do_classifier']) == 'y': + self.set_classifier() + + def _find_files(self): + # we are looking for python modules and packages, + # other stuff are added as regular files + pkgs = self.data['packages'] + modules = self.data['modules'] + extra_files = self.data['extra_files'] + + def is_package(path): + return os.path.exists(os.path.join(path, '__init__.py')) + + curdir = os.getcwd() + scanned = [] + _pref = ['lib', 'include', 'dist', 'build', '.', '~'] + _suf = ['.pyc'] + + def to_skip(path): + path = relative(path) + + for pref in _pref: + if path.startswith(pref): + return True + + for suf in _suf: + if path.endswith(suf): + return True + + return False + + def relative(path): + return path[len(curdir) + 1:] + + def dotted(path): + res = relative(path).replace(os.path.sep, '.') + if res.endswith('.py'): + res = res[:-len('.py')] + return res + + # first pass: packages + for root, dirs, files in os.walk(curdir): + if to_skip(root): + continue + for dir_ in sorted(dirs): + if to_skip(dir_): + continue + fullpath = os.path.join(root, dir_) + dotted_name = dotted(fullpath) + if is_package(fullpath) and dotted_name not in pkgs: + pkgs.append(dotted_name) + scanned.append(fullpath) + + # modules and extra files + for root, dirs, files in os.walk(curdir): + if to_skip(root): + continue + + if any(root.startswith(path) for path in scanned): + continue + + for file in sorted(files): + fullpath = os.path.join(root, file) + if to_skip(fullpath): + continue + # single module? + if os.path.splitext(file)[-1] == '.py': + modules.append(dotted(fullpath)) + else: + extra_files.append(relative(fullpath)) + + def _set_multi(self, question, name): + existing_values = self.data[name] + value = ask(question, helptext=_helptext[name]).strip() + if value not in existing_values: + existing_values.append(value) + + def set_classifier(self): + self.set_maturity_status(self.classifiers) + self.set_license(self.classifiers) + self.set_other_classifier(self.classifiers) + + def set_other_classifier(self, classifiers): + if ask_yn('Do you want to set other trove identifiers', 'n', + _helptext['trove_generic']) != 'y': + return + self.walk_classifiers(classifiers, [CLASSIFIERS], '') + + def walk_classifiers(self, classifiers, trovepath, desc): + trove = trovepath[-1] + + if not trove: + return + + for key in sorted(trove): + if len(trove[key]) == 0: + if ask_yn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y': + classifiers.add(desc[4:] + ' :: ' + key) + continue + + if ask_yn('Do you want to set items under\n "%s" (%d sub-items)' + % (key, len(trove[key])), 'n', + _helptext['trove_generic']) == 'y': + self.walk_classifiers(classifiers, trovepath + [trove[key]], + desc + ' :: ' + key) + + def set_license(self, classifiers): + while True: + license = ask('What license do you use', + helptext=_helptext['trove_license'], required=False) + if not license: + return + + license_words = license.lower().split(' ') + found_list = [] + + for index, licence in LICENCES: + for word in license_words: + if word in licence: + found_list.append(index) + break + + if len(found_list) == 0: + print('ERROR: Could not find a matching license for "%s"' % + license) + continue + + question = 'Matching licenses:\n\n' + + for index, list_index in enumerate(found_list): + question += ' %s) %s\n' % (index + 1, + _CLASSIFIERS_LIST[list_index]) + + question += ('\nType the number of the license you wish to use or ' + '? to try again:') + choice = ask(question, required=False) + + if choice == '?': + continue + if choice == '': + return + + try: + index = found_list[int(choice) - 1] + except ValueError: + print("ERROR: Invalid selection, type a number from the list " + "above.") + + classifiers.add(_CLASSIFIERS_LIST[index]) + + def set_maturity_status(self, classifiers): + maturity_name = lambda mat: mat.split('- ')[-1] + maturity_question = '''\ + Please select the project status: + + %s + + Status''' % '\n'.join('%s - %s' % (i, maturity_name(n)) + for i, n in enumerate(PROJECT_MATURITY)) + while True: + choice = ask(dedent(maturity_question), required=False) + + if choice: + try: + choice = int(choice) - 1 + key = PROJECT_MATURITY[choice] + classifiers.add(key) + return + except (IndexError, ValueError): + print("ERROR: Invalid selection, type a single digit " + "number.") + + +def main(): + """Main entry point.""" + program = MainProgram() + # # uncomment when implemented + # if not program.load_existing_setup_script(): + # program.inspect_directory() + # program.query_user() + # program.update_config_file() + # program.write_setup_script() + # packaging.util.cfg_to_args() + program() + + +if __name__ == '__main__': + main() diff --git a/Lib/packaging/database.py b/Lib/packaging/database.py new file mode 100644 index 0000000000..087a6ecadd --- /dev/null +++ b/Lib/packaging/database.py @@ -0,0 +1,627 @@ +"""PEP 376 implementation.""" + +import io +import os +import re +import csv +import sys +import zipimport +from hashlib import md5 +from packaging import logger +from packaging.errors import PackagingError +from packaging.version import suggest_normalized_version, VersionPredicate +from packaging.metadata import Metadata + + +__all__ = [ + 'Distribution', 'EggInfoDistribution', 'distinfo_dirname', + 'get_distributions', 'get_distribution', 'get_file_users', + 'provides_distribution', 'obsoletes_distribution', + 'enable_cache', 'disable_cache', 'clear_cache', +] + + +# TODO update docs + +DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED', 'RESOURCES') + +# Cache +_cache_name = {} # maps names to Distribution instances +_cache_name_egg = {} # maps names to EggInfoDistribution instances +_cache_path = {} # maps paths to Distribution instances +_cache_path_egg = {} # maps paths to EggInfoDistribution instances +_cache_generated = False # indicates if .dist-info distributions are cached +_cache_generated_egg = False # indicates if .dist-info and .egg are cached +_cache_enabled = True + + +def enable_cache(): + """ + Enables the internal cache. + + Note that this function will not clear the cache in any case, for that + functionality see :func:`clear_cache`. + """ + global _cache_enabled + + _cache_enabled = True + + +def disable_cache(): + """ + Disables the internal cache. + + Note that this function will not clear the cache in any case, for that + functionality see :func:`clear_cache`. + """ + global _cache_enabled + + _cache_enabled = False + + +def clear_cache(): + """ Clears the internal cache. """ + global _cache_name, _cache_name_egg, _cache_path, _cache_path_egg, \ + _cache_generated, _cache_generated_egg + + _cache_name = {} + _cache_name_egg = {} + _cache_path = {} + _cache_path_egg = {} + _cache_generated = False + _cache_generated_egg = False + + +def _yield_distributions(include_dist, include_egg, paths=sys.path): + """ + Yield .dist-info and .egg(-info) distributions, based on the arguments + + :parameter include_dist: yield .dist-info distributions + :parameter include_egg: yield .egg(-info) distributions + """ + for path in paths: + realpath = os.path.realpath(path) + if not os.path.isdir(realpath): + continue + for dir in os.listdir(realpath): + dist_path = os.path.join(realpath, dir) + if include_dist and dir.endswith('.dist-info'): + yield Distribution(dist_path) + elif include_egg and (dir.endswith('.egg-info') or + dir.endswith('.egg')): + yield EggInfoDistribution(dist_path) + + +def _generate_cache(use_egg_info=False, paths=sys.path): + global _cache_generated, _cache_generated_egg + + if _cache_generated_egg or (_cache_generated and not use_egg_info): + return + else: + gen_dist = not _cache_generated + gen_egg = use_egg_info + + for dist in _yield_distributions(gen_dist, gen_egg, paths): + if isinstance(dist, Distribution): + _cache_path[dist.path] = dist + if not dist.name in _cache_name: + _cache_name[dist.name] = [] + _cache_name[dist.name].append(dist) + else: + _cache_path_egg[dist.path] = dist + if not dist.name in _cache_name_egg: + _cache_name_egg[dist.name] = [] + _cache_name_egg[dist.name].append(dist) + + if gen_dist: + _cache_generated = True + if gen_egg: + _cache_generated_egg = True + + +class Distribution: + """Created with the *path* of the ``.dist-info`` directory provided to the + constructor. It reads the metadata contained in ``METADATA`` when it is + instantiated.""" + + name = '' + """The name of the distribution.""" + + version = '' + """The version of the distribution.""" + + metadata = None + """A :class:`packaging.metadata.Metadata` instance loaded with + the distribution's ``METADATA`` file.""" + + requested = False + """A boolean that indicates whether the ``REQUESTED`` metadata file is + present (in other words, whether the package was installed by user + request or it was installed as a dependency).""" + + def __init__(self, path): + if _cache_enabled and path in _cache_path: + self.metadata = _cache_path[path].metadata + else: + metadata_path = os.path.join(path, 'METADATA') + self.metadata = Metadata(path=metadata_path) + + self.name = self.metadata['Name'] + self.version = self.metadata['Version'] + self.path = path + + if _cache_enabled and not path in _cache_path: + _cache_path[path] = self + + def __repr__(self): + return '' % ( + self.name, self.version, self.path) + + def _get_records(self, local=False): + with self.get_distinfo_file('RECORD') as record: + record_reader = csv.reader(record, delimiter=',') + # XXX needs an explaining comment + for row in record_reader: + path, checksum, size = (row[:] + + [None for i in range(len(row), 3)]) + if local: + path = path.replace('/', os.sep) + path = os.path.join(sys.prefix, path) + yield path, checksum, size + + def get_resource_path(self, relative_path): + with self.get_distinfo_file('RESOURCES') as resources_file: + resources_reader = csv.reader(resources_file, delimiter=',') + for relative, destination in resources_reader: + if relative == relative_path: + return destination + raise KeyError( + 'no resource file with relative path %r is installed' % + relative_path) + + def list_installed_files(self, local=False): + """ + Iterates over the ``RECORD`` entries and returns a tuple + ``(path, md5, size)`` for each line. If *local* is ``True``, + the returned path is transformed into a local absolute path. + Otherwise the raw value from RECORD is returned. + + A local absolute path is an absolute path in which occurrences of + ``'/'`` have been replaced by the system separator given by ``os.sep``. + + :parameter local: flag to say if the path should be returned a local + absolute path + + :type local: boolean + :returns: iterator of (path, md5, size) + """ + return self._get_records(local) + + def uses(self, path): + """ + Returns ``True`` if path is listed in ``RECORD``. *path* can be a local + absolute path or a relative ``'/'``-separated path. + + :rtype: boolean + """ + for p, checksum, size in self._get_records(): + local_absolute = os.path.join(sys.prefix, p) + if path == p or path == local_absolute: + return True + return False + + def get_distinfo_file(self, path, binary=False): + """ + Returns a file located under the ``.dist-info`` directory. Returns a + ``file`` instance for the file pointed by *path*. + + :parameter path: a ``'/'``-separated path relative to the + ``.dist-info`` directory or an absolute path; + If *path* is an absolute path and doesn't start + with the ``.dist-info`` directory path, + a :class:`PackagingError` is raised + :type path: string + :parameter binary: If *binary* is ``True``, opens the file in read-only + binary mode (``rb``), otherwise opens it in + read-only mode (``r``). + :rtype: file object + """ + open_flags = 'r' + if binary: + open_flags += 'b' + + # Check if it is an absolute path # XXX use relpath, add tests + if path.find(os.sep) >= 0: + # it's an absolute path? + distinfo_dirname, path = path.split(os.sep)[-2:] + if distinfo_dirname != self.path.split(os.sep)[-1]: + raise PackagingError( + 'dist-info file %r does not belong to the %r %s ' + 'distribution' % (path, self.name, self.version)) + + # The file must be relative + if path not in DIST_FILES: + raise PackagingError('invalid path for a dist-info file: %r' % + path) + + path = os.path.join(self.path, path) + return open(path, open_flags) + + def list_distinfo_files(self, local=False): + """ + Iterates over the ``RECORD`` entries and returns paths for each line if + the path is pointing to a file located in the ``.dist-info`` directory + or one of its subdirectories. + + :parameter local: If *local* is ``True``, each returned path is + transformed into a local absolute path. Otherwise the + raw value from ``RECORD`` is returned. + :type local: boolean + :returns: iterator of paths + """ + for path, checksum, size in self._get_records(local): + yield path + + def __eq__(self, other): + return isinstance(other, Distribution) and self.path == other.path + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +class EggInfoDistribution: + """Created with the *path* of the ``.egg-info`` directory or file provided + to the constructor. It reads the metadata contained in the file itself, or + if the given path happens to be a directory, the metadata is read from the + file ``PKG-INFO`` under that directory.""" + + name = '' + """The name of the distribution.""" + + version = '' + """The version of the distribution.""" + + metadata = None + """A :class:`packaging.metadata.Metadata` instance loaded with + the distribution's ``METADATA`` file.""" + + _REQUIREMENT = re.compile( + r'(?P[-A-Za-z0-9_.]+)\s*' + r'(?P(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' + r'(?P(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' + r'(?P\[.*\])?') + + def __init__(self, path): + self.path = path + if _cache_enabled and path in _cache_path_egg: + self.metadata = _cache_path_egg[path].metadata + self.name = self.metadata['Name'] + self.version = self.metadata['Version'] + return + + # reused from Distribute's pkg_resources + def yield_lines(strs): + """Yield non-empty/non-comment lines of a ``basestring`` + or sequence""" + if isinstance(strs, str): + for s in strs.splitlines(): + s = s.strip() + # skip blank lines/comments + if s and not s.startswith('#'): + yield s + else: + for ss in strs: + for s in yield_lines(ss): + yield s + + requires = None + + if path.endswith('.egg'): + if os.path.isdir(path): + meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + self.metadata = Metadata(path=meta_path) + try: + req_path = os.path.join(path, 'EGG-INFO', 'requires.txt') + with open(req_path, 'r') as fp: + requires = fp.read() + except IOError: + requires = None + else: + # FIXME handle the case where zipfile is not available + zipf = zipimport.zipimporter(path) + fileobj = io.StringIO( + zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8')) + self.metadata = Metadata(fileobj=fileobj) + try: + requires = zipf.get_data('EGG-INFO/requires.txt') + except IOError: + requires = None + self.name = self.metadata['Name'] + self.version = self.metadata['Version'] + + elif path.endswith('.egg-info'): + if os.path.isdir(path): + path = os.path.join(path, 'PKG-INFO') + try: + with open(os.path.join(path, 'requires.txt'), 'r') as fp: + requires = fp.read() + except IOError: + requires = None + self.metadata = Metadata(path=path) + self.name = self.metadata['name'] + self.version = self.metadata['Version'] + + else: + raise ValueError('path must end with .egg-info or .egg, got %r' % + path) + + if requires is not None: + if self.metadata['Metadata-Version'] == '1.1': + # we can't have 1.1 metadata *and* Setuptools requires + for field in ('Obsoletes', 'Requires', 'Provides'): + del self.metadata[field] + + reqs = [] + + if requires is not None: + for line in yield_lines(requires): + if line.startswith('['): + logger.warning( + 'extensions in requires.txt are not supported ' + '(used by %r %s)', self.name, self.version) + break + else: + match = self._REQUIREMENT.match(line.strip()) + if not match: + # this happens when we encounter extras; since they + # are written at the end of the file we just exit + break + else: + if match.group('extras'): + msg = ('extra requirements are not supported ' + '(used by %r %s)', self.name, self.version) + logger.warning(msg, self.name) + name = match.group('name') + version = None + if match.group('first'): + version = match.group('first') + if match.group('rest'): + version += match.group('rest') + version = version.replace(' ', '') # trim spaces + if version is None: + reqs.append(name) + else: + reqs.append('%s (%s)' % (name, version)) + + if len(reqs) > 0: + self.metadata['Requires-Dist'] += reqs + + if _cache_enabled: + _cache_path_egg[self.path] = self + + def __repr__(self): + return '' % ( + self.name, self.version, self.path) + + def list_installed_files(self, local=False): + + def _md5(path): + with open(path, 'rb') as f: + content = f.read() + return md5(content).hexdigest() + + def _size(path): + return os.stat(path).st_size + + path = self.path + if local: + path = path.replace('/', os.sep) + + # XXX What about scripts and data files ? + if os.path.isfile(path): + return [(path, _md5(path), _size(path))] + else: + files = [] + for root, dir, files_ in os.walk(path): + for item in files_: + item = os.path.join(root, item) + files.append((item, _md5(item), _size(item))) + return files + + return [] + + def uses(self, path): + return False + + def __eq__(self, other): + return (isinstance(other, EggInfoDistribution) and + self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +def distinfo_dirname(name, version): + """ + The *name* and *version* parameters are converted into their + filename-escaped form, i.e. any ``'-'`` characters are replaced + with ``'_'`` other than the one in ``'dist-info'`` and the one + separating the name from the version number. + + :parameter name: is converted to a standard distribution name by replacing + any runs of non- alphanumeric characters with a single + ``'-'``. + :type name: string + :parameter version: is converted to a standard version string. Spaces + become dots, and all other non-alphanumeric characters + (except dots) become dashes, with runs of multiple + dashes condensed to a single dash. + :type version: string + :returns: directory name + :rtype: string""" + file_extension = '.dist-info' + name = name.replace('-', '_') + normalized_version = suggest_normalized_version(version) + # Because this is a lookup procedure, something will be returned even if + # it is a version that cannot be normalized + if normalized_version is None: + # Unable to achieve normality? + normalized_version = version + return '-'.join([name, normalized_version]) + file_extension + + +def get_distributions(use_egg_info=False, paths=sys.path): + """ + Provides an iterator that looks for ``.dist-info`` directories in + ``sys.path`` and returns :class:`Distribution` instances for each one of + them. If the parameters *use_egg_info* is ``True``, then the ``.egg-info`` + files and directores are iterated as well. + + :rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution` + instances + """ + if not _cache_enabled: + for dist in _yield_distributions(True, use_egg_info, paths): + yield dist + else: + _generate_cache(use_egg_info, paths) + + for dist in _cache_path.values(): + yield dist + + if use_egg_info: + for dist in _cache_path_egg.values(): + yield dist + + +def get_distribution(name, use_egg_info=False, paths=None): + """ + Scans all elements in ``sys.path`` and looks for all directories + ending with ``.dist-info``. Returns a :class:`Distribution` + corresponding to the ``.dist-info`` directory that contains the + ``METADATA`` that matches *name* for the *name* metadata field. + If no distribution exists with the given *name* and the parameter + *use_egg_info* is set to ``True``, then all files and directories ending + with ``.egg-info`` are scanned. A :class:`EggInfoDistribution` instance is + returned if one is found that has metadata that matches *name* for the + *name* metadata field. + + This function only returns the first result found, as no more than one + value is expected. If the directory is not found, ``None`` is returned. + + :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None + """ + if paths == None: + paths = sys.path + + if not _cache_enabled: + for dist in _yield_distributions(True, use_egg_info, paths): + if dist.name == name: + return dist + else: + _generate_cache(use_egg_info, paths) + + if name in _cache_name: + return _cache_name[name][0] + elif use_egg_info and name in _cache_name_egg: + return _cache_name_egg[name][0] + else: + return None + + +def obsoletes_distribution(name, version=None, use_egg_info=False): + """ + Iterates over all distributions to find which distributions obsolete + *name*. + + If a *version* is provided, it will be used to filter the results. + If the argument *use_egg_info* is set to ``True``, then ``.egg-info`` + distributions will be considered as well. + + :type name: string + :type version: string + :parameter name: + """ + for dist in get_distributions(use_egg_info): + obsoleted = (dist.metadata['Obsoletes-Dist'] + + dist.metadata['Obsoletes']) + for obs in obsoleted: + o_components = obs.split(' ', 1) + if len(o_components) == 1 or version is None: + if name == o_components[0]: + yield dist + break + else: + try: + predicate = VersionPredicate(obs) + except ValueError: + raise PackagingError( + 'distribution %r has ill-formed obsoletes field: ' + '%r' % (dist.name, obs)) + if name == o_components[0] and predicate.match(version): + yield dist + break + + +def provides_distribution(name, version=None, use_egg_info=False): + """ + Iterates over all distributions to find which distributions provide *name*. + If a *version* is provided, it will be used to filter the results. Scans + all elements in ``sys.path`` and looks for all directories ending with + ``.dist-info``. Returns a :class:`Distribution` corresponding to the + ``.dist-info`` directory that contains a ``METADATA`` that matches *name* + for the name metadata. If the argument *use_egg_info* is set to ``True``, + then all files and directories ending with ``.egg-info`` are considered + as well and returns an :class:`EggInfoDistribution` instance. + + This function only returns the first result found, since no more than + one values are expected. If the directory is not found, returns ``None``. + + :parameter version: a version specifier that indicates the version + required, conforming to the format in ``PEP-345`` + + :type name: string + :type version: string + """ + predicate = None + if not version is None: + try: + predicate = VersionPredicate(name + ' (' + version + ')') + except ValueError: + raise PackagingError('invalid name or version: %r, %r' % + (name, version)) + + for dist in get_distributions(use_egg_info): + provided = dist.metadata['Provides-Dist'] + dist.metadata['Provides'] + + for p in provided: + p_components = p.rsplit(' ', 1) + if len(p_components) == 1 or predicate is None: + if name == p_components[0]: + yield dist + break + else: + p_name, p_ver = p_components + if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')': + raise PackagingError( + 'distribution %r has invalid Provides field: %r' % + (dist.name, p)) + p_ver = p_ver[1:-1] # trim off the parenthesis + if p_name == name and predicate.match(p_ver): + yield dist + break + + +def get_file_users(path): + """ + Iterates over all distributions to find out which distributions use + *path*. + + :parameter path: can be a local absolute path or a relative + ``'/'``-separated path. + :type path: string + :rtype: iterator of :class:`Distribution` instances + """ + for dist in get_distributions(): + if dist.uses(path): + yield dist diff --git a/Lib/packaging/depgraph.py b/Lib/packaging/depgraph.py new file mode 100644 index 0000000000..48ea3d9252 --- /dev/null +++ b/Lib/packaging/depgraph.py @@ -0,0 +1,270 @@ +"""Class and functions dealing with dependencies between distributions. + +This module provides a DependencyGraph class to represent the +dependencies between distributions. Auxiliary functions can generate a +graph, find reverse dependencies, and print a graph in DOT format. +""" + +import sys + +from io import StringIO +from packaging.errors import PackagingError +from packaging.version import VersionPredicate, IrrationalVersionError + +__all__ = ['DependencyGraph', 'generate_graph', 'dependent_dists', + 'graph_to_dot'] + + +class DependencyGraph: + """ + Represents a dependency graph between distributions. + + The dependency relationships are stored in an ``adjacency_list`` that maps + distributions to a list of ``(other, label)`` tuples where ``other`` + is a distribution and the edge is labeled with ``label`` (i.e. the version + specifier, if such was provided). Also, for more efficient traversal, for + every distribution ``x``, a list of predecessors is kept in + ``reverse_list[x]``. An edge from distribution ``a`` to + distribution ``b`` means that ``a`` depends on ``b``. If any missing + dependencies are found, they are stored in ``missing``, which is a + dictionary that maps distributions to a list of requirements that were not + provided by any other distributions. + """ + + def __init__(self): + self.adjacency_list = {} + self.reverse_list = {} + self.missing = {} + + def add_distribution(self, distribution): + """Add the *distribution* to the graph. + + :type distribution: :class:`packaging.database.Distribution` or + :class:`packaging.database.EggInfoDistribution` + """ + self.adjacency_list[distribution] = [] + self.reverse_list[distribution] = [] + self.missing[distribution] = [] + + def add_edge(self, x, y, label=None): + """Add an edge from distribution *x* to distribution *y* with the given + *label*. + + :type x: :class:`packaging.database.Distribution` or + :class:`packaging.database.EggInfoDistribution` + :type y: :class:`packaging.database.Distribution` or + :class:`packaging.database.EggInfoDistribution` + :type label: ``str`` or ``None`` + """ + self.adjacency_list[x].append((y, label)) + # multiple edges are allowed, so be careful + if not x in self.reverse_list[y]: + self.reverse_list[y].append(x) + + def add_missing(self, distribution, requirement): + """ + Add a missing *requirement* for the given *distribution*. + + :type distribution: :class:`packaging.database.Distribution` or + :class:`packaging.database.EggInfoDistribution` + :type requirement: ``str`` + """ + self.missing[distribution].append(requirement) + + def _repr_dist(self, dist): + return '%s %s' % (dist.name, dist.metadata['Version']) + + def repr_node(self, dist, level=1): + """Prints only a subgraph""" + output = [] + output.append(self._repr_dist(dist)) + for other, label in self.adjacency_list[dist]: + dist = self._repr_dist(other) + if label is not None: + dist = '%s [%s]' % (dist, label) + output.append(' ' * level + str(dist)) + suboutput = self.repr_node(other, level + 1) + subs = suboutput.split('\n') + output.extend(subs[1:]) + return '\n'.join(output) + + def __repr__(self): + """Representation of the graph""" + output = [] + for dist, adjs in self.adjacency_list.items(): + output.append(self.repr_node(dist)) + return '\n'.join(output) + + +def graph_to_dot(graph, f, skip_disconnected=True): + """Writes a DOT output for the graph to the provided file *f*. + + If *skip_disconnected* is set to ``True``, then all distributions + that are not dependent on any other distribution are skipped. + + :type f: has to support ``file``-like operations + :type skip_disconnected: ``bool`` + """ + disconnected = [] + + f.write("digraph dependencies {\n") + for dist, adjs in graph.adjacency_list.items(): + if len(adjs) == 0 and not skip_disconnected: + disconnected.append(dist) + for other, label in adjs: + if not label is None: + f.write('"%s" -> "%s" [label="%s"]\n' % + (dist.name, other.name, label)) + else: + f.write('"%s" -> "%s"\n' % (dist.name, other.name)) + if not skip_disconnected and len(disconnected) > 0: + f.write('subgraph disconnected {\n') + f.write('label = "Disconnected"\n') + f.write('bgcolor = red\n') + + for dist in disconnected: + f.write('"%s"' % dist.name) + f.write('\n') + f.write('}\n') + f.write('}\n') + + +def generate_graph(dists): + """Generates a dependency graph from the given distributions. + + :parameter dists: a list of distributions + :type dists: list of :class:`packaging.database.Distribution` and + :class:`packaging.database.EggInfoDistribution` instances + :rtype: a :class:`DependencyGraph` instance + """ + graph = DependencyGraph() + provided = {} # maps names to lists of (version, dist) tuples + + # first, build the graph and find out the provides + for dist in dists: + graph.add_distribution(dist) + provides = (dist.metadata['Provides-Dist'] + + dist.metadata['Provides'] + + ['%s (%s)' % (dist.name, dist.metadata['Version'])]) + + for p in provides: + comps = p.strip().rsplit(" ", 1) + name = comps[0] + version = None + if len(comps) == 2: + version = comps[1] + if len(version) < 3 or version[0] != '(' or version[-1] != ')': + raise PackagingError('Distribution %s has ill formed' \ + 'provides field: %s' % (dist.name, p)) + version = version[1:-1] # trim off parenthesis + if not name in provided: + provided[name] = [] + provided[name].append((version, dist)) + + # now make the edges + for dist in dists: + requires = dist.metadata['Requires-Dist'] + dist.metadata['Requires'] + for req in requires: + try: + predicate = VersionPredicate(req) + except IrrationalVersionError: + # XXX compat-mode if cannot read the version + name = req.split()[0] + predicate = VersionPredicate(name) + + name = predicate.name + + if not name in provided: + graph.add_missing(dist, req) + else: + matched = False + for version, provider in provided[name]: + try: + match = predicate.match(version) + except IrrationalVersionError: + # XXX small compat-mode + if version.split(' ') == 1: + match = True + else: + match = False + + if match: + graph.add_edge(dist, provider, req) + matched = True + break + if not matched: + graph.add_missing(dist, req) + return graph + + +def dependent_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + dependent on *dist*. + + :param dists: a list of distributions + :param dist: a distribution, member of *dists* for which we are interested + """ + if not dist in dists: + raise ValueError('The given distribution is not a member of the list') + graph = generate_graph(dists) + + dep = [dist] # dependent distributions + fringe = graph.reverse_list[dist] # list of nodes we should inspect + + while not len(fringe) == 0: + node = fringe.pop() + dep.append(node) + for prev in graph.reverse_list[node]: + if not prev in dep: + fringe.append(prev) + + dep.pop(0) # remove dist from dep, was there to prevent infinite loops + return dep + + +def main(): + from packaging.database import get_distributions + tempout = StringIO() + try: + old = sys.stderr + sys.stderr = tempout + try: + dists = list(get_distributions(use_egg_info=True)) + graph = generate_graph(dists) + finally: + sys.stderr = old + except Exception as e: + tempout.seek(0) + tempout = tempout.read() + print('Could not generate the graph\n%s\n%s\n' % (tempout, e)) + sys.exit(1) + + for dist, reqs in graph.missing.items(): + if len(reqs) > 0: + print("Warning: Missing dependencies for %s:" % dist.name, + ", ".join(reqs)) + # XXX replace with argparse + if len(sys.argv) == 1: + print('Dependency graph:') + print(' ' + repr(graph).replace('\n', '\n ')) + sys.exit(0) + elif len(sys.argv) > 1 and sys.argv[1] in ('-d', '--dot'): + if len(sys.argv) > 2: + filename = sys.argv[2] + else: + filename = 'depgraph.dot' + + with open(filename, 'w') as f: + graph_to_dot(graph, f, True) + tempout.seek(0) + tempout = tempout.read() + print(tempout) + print('Dot file written at "%s"' % filename) + sys.exit(0) + else: + print('Supported option: -d [filename]') + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/Lib/packaging/dist.py b/Lib/packaging/dist.py new file mode 100644 index 0000000000..6065e78a6a --- /dev/null +++ b/Lib/packaging/dist.py @@ -0,0 +1,819 @@ +"""Class representing the distribution being built/installed/etc.""" + +import os +import re + +from packaging.errors import (PackagingOptionError, PackagingArgError, + PackagingModuleError, PackagingClassError) +from packaging.fancy_getopt import FancyGetopt +from packaging.util import strtobool, resolve_name +from packaging import logger +from packaging.metadata import Metadata +from packaging.config import Config +from packaging.command import get_command_class, STANDARD_COMMANDS + +# Regex to define acceptable Packaging command names. This is not *quite* +# the same as a Python NAME -- I don't allow leading underscores. The fact +# that they're very similar is no coincidence; the default naming scheme is +# to look for a Python module named after the command. +command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') + +USAGE = """\ +usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] + or: %(script)s --help [cmd1 cmd2 ...] + or: %(script)s --help-commands + or: %(script)s cmd --help +""" + + +def gen_usage(script_name): + script = os.path.basename(script_name) + return USAGE % {'script': script} + + +class Distribution: + """The core of the Packaging. Most of the work hiding behind 'setup' + is really done within a Distribution instance, which farms the work out + to the Packaging commands specified on the command line. + + Setup scripts will almost never instantiate Distribution directly, + unless the 'setup()' function is totally inadequate to their needs. + However, it is conceivable that a setup script might wish to subclass + Distribution for some specialized purpose, and then pass the subclass + to 'setup()' as the 'distclass' keyword argument. If so, it is + necessary to respect the expectations that 'setup' has of Distribution. + See the code for 'setup()', in run.py, for details. + """ + + # 'global_options' describes the command-line options that may be + # supplied to the setup script prior to any actual commands. + # Eg. "./setup.py -n" or "./setup.py --dry-run" both take advantage of + # these global options. This list should be kept to a bare minimum, + # since every global option is also valid as a command option -- and we + # don't want to pollute the commands with too many options that they + # have minimal control over. + global_options = [ + ('dry-run', 'n', "don't actually do anything"), + ('help', 'h', "show detailed help message"), + ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'), + ] + + # 'common_usage' is a short (2-3 line) string describing the common + # usage of the setup script. + common_usage = """\ +Common commands: (see '--help-commands' for more) + + setup.py build will build the package underneath 'build/' + setup.py install will install the package +""" + + # options that are not propagated to the commands + display_options = [ + ('help-commands', None, + "list all available commands"), + ('name', None, + "print package name"), + ('version', 'V', + "print package version"), + ('fullname', None, + "print -"), + ('author', None, + "print the author's name"), + ('author-email', None, + "print the author's email address"), + ('maintainer', None, + "print the maintainer's name"), + ('maintainer-email', None, + "print the maintainer's email address"), + ('contact', None, + "print the maintainer's name if known, else the author's"), + ('contact-email', None, + "print the maintainer's email address if known, else the author's"), + ('url', None, + "print the URL for this package"), + ('license', None, + "print the license of the package"), + ('licence', None, + "alias for --license"), + ('description', None, + "print the package description"), + ('long-description', None, + "print the long package description"), + ('platforms', None, + "print the list of platforms"), + ('classifier', None, + "print the list of classifiers"), + ('keywords', None, + "print the list of keywords"), + ('provides', None, + "print the list of packages/modules provided"), + ('requires', None, + "print the list of packages/modules required"), + ('obsoletes', None, + "print the list of packages/modules made obsolete"), + ('use-2to3', None, + "use 2to3 to make source python 3.x compatible"), + ('convert-2to3-doctests', None, + "use 2to3 to convert doctests in seperate text files"), + ] + display_option_names = [x[0].replace('-', '_') for x in display_options] + + # negative options are options that exclude other options + negative_opt = {} + + # -- Creation/initialization methods ------------------------------- + def __init__(self, attrs=None): + """Construct a new Distribution instance: initialize all the + attributes of a Distribution, and then use 'attrs' (a dictionary + mapping attribute names to values) to assign some of those + attributes their "real" values. (Any attributes not mentioned in + 'attrs' will be assigned to some null value: 0, None, an empty list + or dictionary, etc.) Most importantly, initialize the + 'command_obj' attribute to the empty dictionary; this will be + filled in with real command objects by 'parse_command_line()'. + """ + + # Default values for our command-line options + self.dry_run = False + self.help = False + for attr in self.display_option_names: + setattr(self, attr, False) + + # Store the configuration + self.config = Config(self) + + # Store the distribution metadata (name, version, author, and so + # forth) in a separate object -- we're getting to have enough + # information here (and enough command-line options) that it's + # worth it. + self.metadata = Metadata() + + # 'cmdclass' maps command names to class objects, so we + # can 1) quickly figure out which class to instantiate when + # we need to create a new command object, and 2) have a way + # for the setup script to override command classes + self.cmdclass = {} + + # 'script_name' and 'script_args' are usually set to sys.argv[0] + # and sys.argv[1:], but they can be overridden when the caller is + # not necessarily a setup script run from the command line. + self.script_name = None + self.script_args = None + + # 'command_options' is where we store command options between + # parsing them (from config files, the command line, etc.) and when + # they are actually needed -- ie. when the command in question is + # instantiated. It is a dictionary of dictionaries of 2-tuples: + # command_options = { command_name : { option : (source, value) } } + self.command_options = {} + + # 'dist_files' is the list of (command, pyversion, file) that + # have been created by any dist commands run so far. This is + # filled regardless of whether the run is dry or not. pyversion + # gives sysconfig.get_python_version() if the dist file is + # specific to a Python version, 'any' if it is good for all + # Python versions on the target platform, and '' for a source + # file. pyversion should not be used to specify minimum or + # maximum required Python versions; use the metainfo for that + # instead. + self.dist_files = [] + + # These options are really the business of various commands, rather + # than of the Distribution itself. We provide aliases for them in + # Distribution as a convenience to the developer. + self.packages = [] + self.package_data = {} + self.package_dir = None + self.py_modules = [] + self.libraries = [] + self.headers = [] + self.ext_modules = [] + self.ext_package = None + self.include_dirs = [] + self.extra_path = None + self.scripts = [] + self.data_files = {} + self.password = '' + self.use_2to3 = False + self.convert_2to3_doctests = [] + self.extra_files = [] + + # And now initialize bookkeeping stuff that can't be supplied by + # the caller at all. 'command_obj' maps command names to + # Command instances -- that's how we enforce that every command + # class is a singleton. + self.command_obj = {} + + # 'have_run' maps command names to boolean values; it keeps track + # of whether we have actually run a particular command, to make it + # cheap to "run" a command whenever we think we might need to -- if + # it's already been done, no need for expensive filesystem + # operations, we just check the 'have_run' dictionary and carry on. + # It's only safe to query 'have_run' for a command class that has + # been instantiated -- a false value will be inserted when the + # command object is created, and replaced with a true value when + # the command is successfully run. Thus it's probably best to use + # '.get()' rather than a straight lookup. + self.have_run = {} + + # Now we'll use the attrs dictionary (ultimately, keyword args from + # the setup script) to possibly override any or all of these + # distribution options. + + if attrs is not None: + # Pull out the set of command options and work on them + # specifically. Note that this order guarantees that aliased + # command options will override any supplied redundantly + # through the general options dictionary. + options = attrs.get('options') + if options is not None: + del attrs['options'] + for command, cmd_options in options.items(): + opt_dict = self.get_option_dict(command) + for opt, val in cmd_options.items(): + opt_dict[opt] = ("setup script", val) + + # Now work on the rest of the attributes. Any attribute that's + # not already defined is invalid! + for key, val in attrs.items(): + if self.metadata.is_metadata_field(key): + self.metadata[key] = val + elif hasattr(self, key): + setattr(self, key, val) + else: + logger.warning( + 'unknown argument given to Distribution: %r', key) + + # no-user-cfg is handled before other command line args + # because other args override the config files, and this + # one is needed before we can load the config files. + # If attrs['script_args'] wasn't passed, assume false. + # + # This also make sure we just look at the global options + self.want_user_cfg = True + + if self.script_args is not None: + for arg in self.script_args: + if not arg.startswith('-'): + break + if arg == '--no-user-cfg': + self.want_user_cfg = False + break + + self.finalize_options() + + def get_option_dict(self, command): + """Get the option dictionary for a given command. If that + command's option dictionary hasn't been created yet, then create it + and return the new dictionary; otherwise, return the existing + option dictionary. + """ + d = self.command_options.get(command) + if d is None: + d = self.command_options[command] = {} + return d + + def get_fullname(self): + return self.metadata.get_fullname() + + def dump_option_dicts(self, header=None, commands=None, indent=""): + from pprint import pformat + + if commands is None: # dump all command option dicts + commands = sorted(self.command_options) + + if header is not None: + logger.info(indent + header) + indent = indent + " " + + if not commands: + logger.info(indent + "no commands known yet") + return + + for cmd_name in commands: + opt_dict = self.command_options.get(cmd_name) + if opt_dict is None: + logger.info(indent + "no option dict for %r command", + cmd_name) + else: + logger.info(indent + "option dict for %r command:", cmd_name) + out = pformat(opt_dict) + for line in out.split('\n'): + logger.info(indent + " " + line) + + # -- Config file finding/parsing methods --------------------------- + # XXX to be removed + def parse_config_files(self, filenames=None): + return self.config.parse_config_files(filenames) + + def find_config_files(self): + return self.config.find_config_files() + + # -- Command-line parsing methods ---------------------------------- + + def parse_command_line(self): + """Parse the setup script's command line, taken from the + 'script_args' instance attribute (which defaults to 'sys.argv[1:]' + -- see 'setup()' in run.py). This list is first processed for + "global options" -- options that set attributes of the Distribution + instance. Then, it is alternately scanned for Packaging commands + and options for that command. Each new command terminates the + options for the previous command. The allowed options for a + command are determined by the 'user_options' attribute of the + command class -- thus, we have to be able to load command classes + in order to parse the command line. Any error in that 'options' + attribute raises PackagingGetoptError; any error on the + command line raises PackagingArgError. If no Packaging commands + were found on the command line, raises PackagingArgError. Return + true if command line was successfully parsed and we should carry + on with executing commands; false if no errors but we shouldn't + execute commands (currently, this only happens if user asks for + help). + """ + # + # We now have enough information to show the Macintosh dialog + # that allows the user to interactively specify the "command line". + # + toplevel_options = self._get_toplevel_options() + + # We have to parse the command line a bit at a time -- global + # options, then the first command, then its options, and so on -- + # because each command will be handled by a different class, and + # the options that are valid for a particular class aren't known + # until we have loaded the command class, which doesn't happen + # until we know what the command is. + + self.commands = [] + parser = FancyGetopt(toplevel_options + self.display_options) + parser.set_negative_aliases(self.negative_opt) + parser.set_aliases({'licence': 'license'}) + args = parser.getopt(args=self.script_args, object=self) + option_order = parser.get_option_order() + + # for display options we return immediately + if self.handle_display_options(option_order): + return + + while args: + args = self._parse_command_opts(parser, args) + if args is None: # user asked for help (and got it) + return + + # Handle the cases of --help as a "global" option, ie. + # "setup.py --help" and "setup.py --help command ...". For the + # former, we show global options (--dry-run, etc.) + # and display-only options (--name, --version, etc.); for the + # latter, we omit the display-only options and show help for + # each command listed on the command line. + if self.help: + self._show_help(parser, + display_options=len(self.commands) == 0, + commands=self.commands) + return + + return 1 + + def _get_toplevel_options(self): + """Return the non-display options recognized at the top level. + + This includes options that are recognized *only* at the top + level as well as options recognized for commands. + """ + return self.global_options + + def _parse_command_opts(self, parser, args): + """Parse the command-line options for a single command. + 'parser' must be a FancyGetopt instance; 'args' must be the list + of arguments, starting with the current command (whose options + we are about to parse). Returns a new version of 'args' with + the next command at the front of the list; will be the empty + list if there are no more commands on the command line. Returns + None if the user asked for help on this command. + """ + # Pull the current command from the head of the command line + command = args[0] + if not command_re.match(command): + raise SystemExit("invalid command name %r" % command) + self.commands.append(command) + + # Dig up the command class that implements this command, so we + # 1) know that it's a valid command, and 2) know which options + # it takes. + try: + cmd_class = get_command_class(command) + except PackagingModuleError as msg: + raise PackagingArgError(msg) + + # XXX We want to push this in packaging.command + # + # Require that the command class be derived from Command -- want + # to be sure that the basic "command" interface is implemented. + for meth in ('initialize_options', 'finalize_options', 'run'): + if hasattr(cmd_class, meth): + continue + raise PackagingClassError( + 'command %r must implement %r' % (cmd_class, meth)) + + # Also make sure that the command object provides a list of its + # known options. + if not (hasattr(cmd_class, 'user_options') and + isinstance(cmd_class.user_options, list)): + raise PackagingClassError( + "command class %s must provide " + "'user_options' attribute (a list of tuples)" % cmd_class) + + # If the command class has a list of negative alias options, + # merge it in with the global negative aliases. + negative_opt = self.negative_opt + if hasattr(cmd_class, 'negative_opt'): + negative_opt = negative_opt.copy() + negative_opt.update(cmd_class.negative_opt) + + # Check for help_options in command class. They have a different + # format (tuple of four) so we need to preprocess them here. + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_options = cmd_class.help_options[:] + else: + help_options = [] + + # All commands support the global options too, just by adding + # in 'global_options'. + parser.set_option_table(self.global_options + + cmd_class.user_options + + help_options) + parser.set_negative_aliases(negative_opt) + args, opts = parser.getopt(args[1:]) + if hasattr(opts, 'help') and opts.help: + self._show_help(parser, display_options=False, + commands=[cmd_class]) + return + + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_option_found = False + for help_option, short, desc, func in cmd_class.help_options: + if hasattr(opts, help_option.replace('-', '_')): + help_option_found = True + if hasattr(func, '__call__'): + func() + else: + raise PackagingClassError( + "invalid help function %r for help option %r: " + "must be a callable object (function, etc.)" + % (func, help_option)) + + if help_option_found: + return + + # Put the options from the command line into their official + # holding pen, the 'command_options' dictionary. + opt_dict = self.get_option_dict(command) + for name, value in vars(opts).items(): + opt_dict[name] = ("command line", value) + + return args + + def finalize_options(self): + """Set final values for all the options on the Distribution + instance, analogous to the .finalize_options() method of Command + objects. + """ + if getattr(self, 'convert_2to3_doctests', None): + self.convert_2to3_doctests = [os.path.join(p) + for p in self.convert_2to3_doctests] + else: + self.convert_2to3_doctests = [] + + def _show_help(self, parser, global_options=True, display_options=True, + commands=[]): + """Show help for the setup script command line in the form of + several lists of command-line options. 'parser' should be a + FancyGetopt instance; do not expect it to be returned in the + same state, as its option table will be reset to make it + generate the correct help text. + + If 'global_options' is true, lists the global options: + --dry-run, etc. If 'display_options' is true, lists + the "display-only" options: --name, --version, etc. Finally, + lists per-command help for every command name or command class + in 'commands'. + """ + # late import because of mutual dependence between these modules + from packaging.command.cmd import Command + + if global_options: + if display_options: + options = self._get_toplevel_options() + else: + options = self.global_options + parser.set_option_table(options) + parser.print_help(self.common_usage + "\nGlobal options:") + print('') + + if display_options: + parser.set_option_table(self.display_options) + parser.print_help( + "Information display options (just display " + + "information, ignore any commands)") + print('') + + for command in self.commands: + if isinstance(command, type) and issubclass(command, Command): + cls = command + else: + cls = get_command_class(command) + if (hasattr(cls, 'help_options') and + isinstance(cls.help_options, list)): + parser.set_option_table(cls.user_options + cls.help_options) + else: + parser.set_option_table(cls.user_options) + parser.print_help("Options for %r command:" % cls.__name__) + print('') + + print(gen_usage(self.script_name)) + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + # User just wants a list of commands -- we'll print it out and stop + # processing now (ie. if they ran "setup --help-commands foo bar", + # we ignore "foo bar"). + if self.help_commands: + self.print_commands() + print('') + print(gen_usage(self.script_name)) + return 1 + + # If user supplied any of the "display metadata" options, then + # display that metadata in the order in which the user supplied the + # metadata options. + any_display_options = False + is_display_option = set() + for option in self.display_options: + is_display_option.add(option[0]) + + for opt, val in option_order: + if val and opt in is_display_option: + opt = opt.replace('-', '_') + value = self.metadata[opt] + if opt in ('keywords', 'platform'): + print(','.join(value)) + elif opt in ('classifier', 'provides', 'requires', + 'obsoletes'): + print('\n'.join(value)) + else: + print(value) + any_display_options = True + + return any_display_options + + def print_command_list(self, commands, header, max_length): + """Print a subset of the list of all commands -- used by + 'print_commands()'. + """ + print(header + ":") + + for cmd in commands: + cls = self.cmdclass.get(cmd) or get_command_class(cmd) + description = getattr(cls, 'description', + '(no description available)') + + print(" %-*s %s" % (max_length, cmd, description)) + + def _get_command_groups(self): + """Helper function to retrieve all the command class names divided + into standard commands (listed in + packaging2.command.STANDARD_COMMANDS) and extra commands (given in + self.cmdclass and not standard commands). + """ + extra_commands = [cmd for cmd in self.cmdclass + if cmd not in STANDARD_COMMANDS] + return STANDARD_COMMANDS, extra_commands + + def print_commands(self): + """Print out a help message listing all available commands with a + description of each. The list is divided into standard commands + (listed in packaging2.command.STANDARD_COMMANDS) and extra commands + (given in self.cmdclass and not standard commands). The + descriptions come from the command class attribute + 'description'. + """ + std_commands, extra_commands = self._get_command_groups() + max_length = 0 + for cmd in (std_commands + extra_commands): + if len(cmd) > max_length: + max_length = len(cmd) + + self.print_command_list(std_commands, + "Standard commands", + max_length) + if extra_commands: + print() + self.print_command_list(extra_commands, + "Extra commands", + max_length) + + # -- Command class/object methods ---------------------------------- + + def get_command_obj(self, command, create=True): + """Return the command object for 'command'. Normally this object + is cached on a previous call to 'get_command_obj()'; if no command + object for 'command' is in the cache, then we either create and + return it (if 'create' is true) or return None. + """ + cmd_obj = self.command_obj.get(command) + if not cmd_obj and create: + logger.debug("Distribution.get_command_obj(): " \ + "creating %r command object", command) + + cls = get_command_class(command) + cmd_obj = self.command_obj[command] = cls(self) + self.have_run[command] = 0 + + # Set any options that were supplied in config files + # or on the command line. (NB. support for error + # reporting is lame here: any errors aren't reported + # until 'finalize_options()' is called, which means + # we won't report the source of the error.) + options = self.command_options.get(command) + if options: + self._set_command_options(cmd_obj, options) + + return cmd_obj + + def _set_command_options(self, command_obj, option_dict=None): + """Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + logger.debug(" setting options for %r command:", command_name) + + for option, (source, value) in option_dict.items(): + logger.debug(" %s = %s (from %s)", option, value, source) + try: + bool_opts = [x.replace('-', '_') + for x in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, str) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise PackagingOptionError( + "error in %s: command %r has no such option %r" % + (source, command_name, option)) + except ValueError as msg: + raise PackagingOptionError(msg) + + def get_reinitialized_command(self, command, reinit_subcommands=False): + """Reinitializes a command to the state it was in when first + returned by 'get_command_obj()': ie., initialized but not yet + finalized. This provides the opportunity to sneak option + values in programmatically, overriding or supplementing + user-supplied values from the config files and command line. + You'll have to re-finalize the command object (by calling + 'finalize_options()' or 'ensure_finalized()') before using it for + real. + + 'command' should be a command name (string) or command object. If + 'reinit_subcommands' is true, also reinitializes the command's + sub-commands, as declared by the 'sub_commands' class attribute (if + it has one). See the "install_dist" command for an example. Only + reinitializes the sub-commands that actually matter, ie. those + whose test predicates return true. + + Returns the reinitialized command object. + """ + from packaging.command.cmd import Command + if not isinstance(command, Command): + command_name = command + command = self.get_command_obj(command_name) + else: + command_name = command.get_command_name() + + if not command.finalized: + return command + command.initialize_options() + self.have_run[command_name] = 0 + command.finalized = False + self._set_command_options(command) + + if reinit_subcommands: + for sub in command.get_sub_commands(): + self.get_reinitialized_command(sub, reinit_subcommands) + + return command + + # -- Methods that operate on the Distribution ---------------------- + + def run_commands(self): + """Run each command that was seen on the setup script command line. + Uses the list of commands found and cache of command objects + created by 'get_command_obj()'. + """ + for cmd in self.commands: + self.run_command(cmd) + + # -- Methods that operate on its Commands -------------------------- + + def run_command(self, command, options=None): + """Do whatever it takes to run a command (including nothing at all, + if the command has already been run). Specifically: if we have + already created and run the command named by 'command', return + silently without doing anything. If the command named by 'command' + doesn't even have a command object yet, create one. Then invoke + 'run()' on that command object (or an existing one). + """ + # Already been here, done that? then return silently. + if self.have_run.get(command): + return + + if options is not None: + self.command_options[command] = options + + cmd_obj = self.get_command_obj(command) + cmd_obj.ensure_finalized() + self.run_command_hooks(cmd_obj, 'pre_hook') + logger.info("running %s", command) + cmd_obj.run() + self.run_command_hooks(cmd_obj, 'post_hook') + self.have_run[command] = 1 + + def run_command_hooks(self, cmd_obj, hook_kind): + """Run hooks registered for that command and phase. + + *cmd_obj* is a finalized command object; *hook_kind* is either + 'pre_hook' or 'post_hook'. + """ + if hook_kind not in ('pre_hook', 'post_hook'): + raise ValueError('invalid hook kind: %r' % hook_kind) + + hooks = getattr(cmd_obj, hook_kind, None) + + if hooks is None: + return + + for hook in hooks.values(): + if isinstance(hook, str): + try: + hook_obj = resolve_name(hook) + except ImportError as e: + raise PackagingModuleError(e) + else: + hook_obj = hook + + if not hasattr(hook_obj, '__call__'): + raise PackagingOptionError('hook %r is not callable' % hook) + + logger.info('running %s %s for command %s', + hook_kind, hook, cmd_obj.get_command_name()) + hook_obj(cmd_obj) + + # -- Distribution query methods ------------------------------------ + def has_pure_modules(self): + return len(self.packages or self.py_modules or []) > 0 + + def has_ext_modules(self): + return self.ext_modules and len(self.ext_modules) > 0 + + def has_c_libraries(self): + return self.libraries and len(self.libraries) > 0 + + def has_modules(self): + return self.has_pure_modules() or self.has_ext_modules() + + def has_headers(self): + return self.headers and len(self.headers) > 0 + + def has_scripts(self): + return self.scripts and len(self.scripts) > 0 + + def has_data_files(self): + return self.data_files and len(self.data_files) > 0 + + def is_pure(self): + return (self.has_pure_modules() and + not self.has_ext_modules() and + not self.has_c_libraries()) diff --git a/Lib/packaging/errors.py b/Lib/packaging/errors.py new file mode 100644 index 0000000000..8924a2dcfc --- /dev/null +++ b/Lib/packaging/errors.py @@ -0,0 +1,142 @@ +"""Exceptions used throughout the package. + +Submodules of packaging may raise exceptions defined in this module as +well as standard exceptions; in particular, SystemExit is usually raised +for errors that are obviously the end-user's fault (e.g. bad +command-line arguments). +""" + + +class PackagingError(Exception): + """The root of all Packaging evil.""" + + +class PackagingModuleError(PackagingError): + """Unable to load an expected module, or to find an expected class + within some module (in particular, command modules and classes).""" + + +class PackagingClassError(PackagingError): + """Some command class (or possibly distribution class, if anyone + feels a need to subclass Distribution) is found not to be holding + up its end of the bargain, ie. implementing some part of the + "command "interface.""" + + +class PackagingGetoptError(PackagingError): + """The option table provided to 'fancy_getopt()' is bogus.""" + + +class PackagingArgError(PackagingError): + """Raised by fancy_getopt in response to getopt.error -- ie. an + error in the command line usage.""" + + +class PackagingFileError(PackagingError): + """Any problems in the filesystem: expected file not found, etc. + Typically this is for problems that we detect before IOError or + OSError could be raised.""" + + +class PackagingOptionError(PackagingError): + """Syntactic/semantic errors in command options, such as use of + mutually conflicting options, or inconsistent options, + badly-spelled values, etc. No distinction is made between option + values originating in the setup script, the command line, config + files, or what-have-you -- but if we *know* something originated in + the setup script, we'll raise PackagingSetupError instead.""" + + +class PackagingSetupError(PackagingError): + """For errors that can be definitely blamed on the setup script, + such as invalid keyword arguments to 'setup()'.""" + + +class PackagingPlatformError(PackagingError): + """We don't know how to do something on the current platform (but + we do know how to do it on some platform) -- eg. trying to compile + C files on a platform not supported by a CCompiler subclass.""" + + +class PackagingExecError(PackagingError): + """Any problems executing an external program (such as the C + compiler, when compiling C files).""" + + +class PackagingInternalError(PackagingError): + """Internal inconsistencies or impossibilities (obviously, this + should never be seen if the code is working!).""" + + +class PackagingTemplateError(PackagingError): + """Syntax error in a file list template.""" + + +class PackagingByteCompileError(PackagingError): + """Byte compile error.""" + + +class PackagingPyPIError(PackagingError): + """Any problem occuring during using the indexes.""" + + +# Exception classes used by the CCompiler implementation classes +class CCompilerError(Exception): + """Some compile/link operation failed.""" + + +class PreprocessError(CCompilerError): + """Failure to preprocess one or more C/C++ files.""" + + +class CompileError(CCompilerError): + """Failure to compile one or more C/C++ source files.""" + + +class LibError(CCompilerError): + """Failure to create a static library from one or more C/C++ object + files.""" + + +class LinkError(CCompilerError): + """Failure to link one or more C/C++ object files into an executable + or shared library file.""" + + +class UnknownFileError(CCompilerError): + """Attempt to process an unknown file type.""" + + +class MetadataMissingError(PackagingError): + """A required metadata is missing""" + + +class MetadataConflictError(PackagingError): + """Attempt to read or write metadata fields that are conflictual.""" + + +class MetadataUnrecognizedVersionError(PackagingError): + """Unknown metadata version number.""" + + +class IrrationalVersionError(Exception): + """This is an irrational version.""" + pass + + +class HugeMajorVersionNumError(IrrationalVersionError): + """An irrational version because the major version number is huge + (often because a year or date was used). + + See `error_on_huge_major_num` option in `NormalizedVersion` for details. + This guard can be disabled by setting that option False. + """ + pass + + +class InstallationException(Exception): + """Base exception for installation scripts""" + + +class InstallationConflict(InstallationException): + """Raised when a conflict is detected""" diff --git a/Lib/packaging/fancy_getopt.py b/Lib/packaging/fancy_getopt.py new file mode 100644 index 0000000000..0490864290 --- /dev/null +++ b/Lib/packaging/fancy_getopt.py @@ -0,0 +1,451 @@ +"""Command line parsing machinery. + +The FancyGetopt class is a Wrapper around the getopt module that +provides the following additional features: + * short and long options are tied together + * options have help strings, so fancy_getopt could potentially + create a complete usage summary + * options set attributes of a passed-in object. + +It is used under the hood by the command classes. Do not use directly. +""" + +import getopt +import re +import sys +import string +import textwrap + +from packaging.errors import PackagingGetoptError, PackagingArgError + +# Much like command_re in packaging.core, this is close to but not quite +# the same as a Python NAME -- except, in the spirit of most GNU +# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!) +# The similarities to NAME are again not a coincidence... +longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)' +longopt_re = re.compile(r'^%s$' % longopt_pat) + +# For recognizing "negative alias" options, eg. "quiet=!verbose" +neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat)) + + +class FancyGetopt: + """Wrapper around the standard 'getopt()' module that provides some + handy extra functionality: + * short and long options are tied together + * options have help strings, and help text can be assembled + from them + * options set attributes of a passed-in object + * boolean options can have "negative aliases" -- eg. if + --quiet is the "negative alias" of --verbose, then "--quiet" + on the command line sets 'verbose' to false + """ + + def __init__(self, option_table=None): + + # The option table is (currently) a list of tuples. The + # tuples may have 3 or four values: + # (long_option, short_option, help_string [, repeatable]) + # if an option takes an argument, its long_option should have '=' + # appended; short_option should just be a single character, no ':' + # in any case. If a long_option doesn't have a corresponding + # short_option, short_option should be None. All option tuples + # must have long options. + self.option_table = option_table + + # 'option_index' maps long option names to entries in the option + # table (ie. those 3-tuples). + self.option_index = {} + if self.option_table: + self._build_index() + + # 'alias' records (duh) alias options; {'foo': 'bar'} means + # --foo is an alias for --bar + self.alias = {} + + # 'negative_alias' keeps track of options that are the boolean + # opposite of some other option + self.negative_alias = {} + + # These keep track of the information in the option table. We + # don't actually populate these structures until we're ready to + # parse the command line, since the 'option_table' passed in here + # isn't necessarily the final word. + self.short_opts = [] + self.long_opts = [] + self.short2long = {} + self.attr_name = {} + self.takes_arg = {} + + # And 'option_order' is filled up in 'getopt()'; it records the + # original order of options (and their values) on the command line, + # but expands short options, converts aliases, etc. + self.option_order = [] + + def _build_index(self): + self.option_index.clear() + for option in self.option_table: + self.option_index[option[0]] = option + + def set_option_table(self, option_table): + self.option_table = option_table + self._build_index() + + def add_option(self, long_option, short_option=None, help_string=None): + if long_option in self.option_index: + raise PackagingGetoptError( + "option conflict: already an option '%s'" % long_option) + else: + option = (long_option, short_option, help_string) + self.option_table.append(option) + self.option_index[long_option] = option + + def has_option(self, long_option): + """Return true if the option table for this parser has an + option with long name 'long_option'.""" + return long_option in self.option_index + + def _check_alias_dict(self, aliases, what): + assert isinstance(aliases, dict) + for alias, opt in aliases.items(): + if alias not in self.option_index: + raise PackagingGetoptError( + ("invalid %s '%s': " + "option '%s' not defined") % (what, alias, alias)) + if opt not in self.option_index: + raise PackagingGetoptError( + ("invalid %s '%s': " + "aliased option '%s' not defined") % (what, alias, opt)) + + def set_aliases(self, alias): + """Set the aliases for this option parser.""" + self._check_alias_dict(alias, "alias") + self.alias = alias + + def set_negative_aliases(self, negative_alias): + """Set the negative aliases for this option parser. + 'negative_alias' should be a dictionary mapping option names to + option names, both the key and value must already be defined + in the option table.""" + self._check_alias_dict(negative_alias, "negative alias") + self.negative_alias = negative_alias + + def _grok_option_table(self): + """Populate the various data structures that keep tabs on the + option table. Called by 'getopt()' before it can do anything + worthwhile. + """ + self.long_opts = [] + self.short_opts = [] + self.short2long.clear() + self.repeat = {} + + for option in self.option_table: + if len(option) == 3: + integer, short, help = option + repeat = 0 + elif len(option) == 4: + integer, short, help, repeat = option + else: + # the option table is part of the code, so simply + # assert that it is correct + raise ValueError("invalid option tuple: %r" % option) + + # Type- and value-check the option names + if not isinstance(integer, str) or len(integer) < 2: + raise PackagingGetoptError( + ("invalid long option '%s': " + "must be a string of length >= 2") % integer) + + if (not ((short is None) or + (isinstance(short, str) and len(short) == 1))): + raise PackagingGetoptError( + ("invalid short option '%s': " + "must be a single character or None") % short) + + self.repeat[integer] = repeat + self.long_opts.append(integer) + + if integer[-1] == '=': # option takes an argument? + if short: + short = short + ':' + integer = integer[0:-1] + self.takes_arg[integer] = 1 + else: + + # Is option is a "negative alias" for some other option (eg. + # "quiet" == "!verbose")? + alias_to = self.negative_alias.get(integer) + if alias_to is not None: + if self.takes_arg[alias_to]: + raise PackagingGetoptError( + ("invalid negative alias '%s': " + "aliased option '%s' takes a value") % \ + (integer, alias_to)) + + self.long_opts[-1] = integer # XXX redundant?! + self.takes_arg[integer] = 0 + + else: + self.takes_arg[integer] = 0 + + # If this is an alias option, make sure its "takes arg" flag is + # the same as the option it's aliased to. + alias_to = self.alias.get(integer) + if alias_to is not None: + if self.takes_arg[integer] != self.takes_arg[alias_to]: + raise PackagingGetoptError( + ("invalid alias '%s': inconsistent with " + "aliased option '%s' (one of them takes a value, " + "the other doesn't") % (integer, alias_to)) + + # Now enforce some bondage on the long option name, so we can + # later translate it to an attribute name on some object. Have + # to do this a bit late to make sure we've removed any trailing + # '='. + if not longopt_re.match(integer): + raise PackagingGetoptError( + ("invalid long option name '%s' " + + "(must be letters, numbers, hyphens only") % integer) + + self.attr_name[integer] = integer.replace('-', '_') + if short: + self.short_opts.append(short) + self.short2long[short[0]] = integer + + def getopt(self, args=None, object=None): + """Parse command-line options in args. Store as attributes on object. + + If 'args' is None or not supplied, uses 'sys.argv[1:]'. If + 'object' is None or not supplied, creates a new OptionDummy + object, stores option values there, and returns a tuple (args, + object). If 'object' is supplied, it is modified in place and + 'getopt()' just returns 'args'; in both cases, the returned + 'args' is a modified copy of the passed-in 'args' list, which + is left untouched. + """ + if args is None: + args = sys.argv[1:] + if object is None: + object = OptionDummy() + created_object = 1 + else: + created_object = 0 + + self._grok_option_table() + + short_opts = ' '.join(self.short_opts) + + try: + opts, args = getopt.getopt(args, short_opts, self.long_opts) + except getopt.error as msg: + raise PackagingArgError(msg) + + for opt, val in opts: + if len(opt) == 2 and opt[0] == '-': # it's a short option + opt = self.short2long[opt[1]] + else: + assert len(opt) > 2 and opt[:2] == '--' + opt = opt[2:] + + alias = self.alias.get(opt) + if alias: + opt = alias + + if not self.takes_arg[opt]: # boolean option? + assert val == '', "boolean option can't have value" + alias = self.negative_alias.get(opt) + if alias: + opt = alias + val = 0 + else: + val = 1 + + attr = self.attr_name[opt] + # The only repeating option at the moment is 'verbose'. + # It has a negative option -q quiet, which should set verbose = 0. + if val and self.repeat.get(attr) is not None: + val = getattr(object, attr, 0) + 1 + setattr(object, attr, val) + self.option_order.append((opt, val)) + + # for opts + if created_object: + return args, object + else: + return args + + def get_option_order(self): + """Returns the list of (option, value) tuples processed by the + previous run of 'getopt()'. Raises RuntimeError if + 'getopt()' hasn't been called yet. + """ + if self.option_order is None: + raise RuntimeError("'getopt()' hasn't been called yet") + else: + return self.option_order + + return self.option_order + + def generate_help(self, header=None): + """Generate help text (a list of strings, one per suggested line of + output) from the option table for this FancyGetopt object. + """ + # Blithely assume the option table is good: probably wouldn't call + # 'generate_help()' unless you've already called 'getopt()'. + + # First pass: determine maximum length of long option names + max_opt = 0 + for option in self.option_table: + integer = option[0] + short = option[1] + l = len(integer) + if integer[-1] == '=': + l = l - 1 + if short is not None: + l = l + 5 # " (-x)" where short == 'x' + if l > max_opt: + max_opt = l + + opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter + + # Typical help block looks like this: + # --foo controls foonabulation + # Help block for longest option looks like this: + # --flimflam set the flim-flam level + # and with wrapped text: + # --flimflam set the flim-flam level (must be between + # 0 and 100, except on Tuesdays) + # Options with short names will have the short name shown (but + # it doesn't contribute to max_opt): + # --foo (-f) controls foonabulation + # If adding the short option would make the left column too wide, + # we push the explanation off to the next line + # --flimflam (-l) + # set the flim-flam level + # Important parameters: + # - 2 spaces before option block start lines + # - 2 dashes for each long option name + # - min. 2 spaces between option and explanation (gutter) + # - 5 characters (incl. space) for short option name + + # Now generate lines of help text. (If 80 columns were good enough + # for Jesus, then 78 columns are good enough for me!) + line_width = 78 + text_width = line_width - opt_width + big_indent = ' ' * opt_width + if header: + lines = [header] + else: + lines = ['Option summary:'] + + for option in self.option_table: + integer, short, help = option[:3] + text = textwrap.wrap(help, text_width) + + # Case 1: no short option at all (makes life easy) + if short is None: + if text: + lines.append(" --%-*s %s" % (max_opt, integer, text[0])) + else: + lines.append(" --%-*s " % (max_opt, integer)) + + # Case 2: we have a short option, so we have to include it + # just after the long option + else: + opt_names = "%s (-%s)" % (integer, short) + if text: + lines.append(" --%-*s %s" % + (max_opt, opt_names, text[0])) + else: + lines.append(" --%-*s" % opt_names) + + for l in text[1:]: + lines.append(big_indent + l) + + return lines + + def print_help(self, header=None, file=None): + if file is None: + file = sys.stdout + for line in self.generate_help(header): + file.write(line + "\n") + + +def fancy_getopt(options, negative_opt, object, args): + parser = FancyGetopt(options) + parser.set_negative_aliases(negative_opt) + return parser.getopt(args, object) + + +WS_TRANS = str.maketrans(string.whitespace, ' ' * len(string.whitespace)) + + +def wrap_text(text, width): + """Split *text* into lines of no more than *width* characters each. + + *text* is a str and *width* an int. Returns a list of str. + """ + + if text is None: + return [] + if len(text) <= width: + return [text] + + text = text.expandtabs() + text = text.translate(WS_TRANS) + + chunks = re.split(r'( +|-+)', text) + chunks = [_f for _f in chunks if _f] # ' - ' results in empty strings + lines = [] + + while chunks: + + cur_line = [] # list of chunks (to-be-joined) + cur_len = 0 # length of current line + + while chunks: + l = len(chunks[0]) + if cur_len + l <= width: # can squeeze (at least) this chunk in + cur_line.append(chunks[0]) + del chunks[0] + cur_len = cur_len + l + else: # this line is full + # drop last chunk if all space + if cur_line and cur_line[-1][0] == ' ': + del cur_line[-1] + break + + if chunks: # any chunks left to process? + + # if the current line is still empty, then we had a single + # chunk that's too big too fit on a line -- so we break + # down and break it up at the line width + if cur_len == 0: + cur_line.append(chunks[0][0:width]) + chunks[0] = chunks[0][width:] + + # all-whitespace chunks at the end of a line can be discarded + # (and we know from the re.split above that if a chunk has + # *any* whitespace, it is *all* whitespace) + if chunks[0][0] == ' ': + del chunks[0] + + # and store this line in the list-of-all-lines -- as a single + # string, of course! + lines.append(''.join(cur_line)) + + # while chunks + + return lines + + +class OptionDummy: + """Dummy class just used as a place to hold command-line option + values as instance attributes.""" + + def __init__(self, options=[]): + """Create a new OptionDummy instance. The attributes listed in + 'options' will be initialized to None.""" + for opt in options: + setattr(self, opt, None) diff --git a/Lib/packaging/install.py b/Lib/packaging/install.py new file mode 100644 index 0000000000..3904727d45 --- /dev/null +++ b/Lib/packaging/install.py @@ -0,0 +1,483 @@ +"""Building blocks for installers. + +When used as a script, this module installs a release thanks to info +obtained from an index (e.g. PyPI), with dependencies. + +This is a higher-level module built on packaging.database and +packaging.pypi. +""" + +import os +import sys +import stat +import errno +import shutil +import logging +import tempfile +from sysconfig import get_config_var + +from packaging import logger +from packaging.dist import Distribution +from packaging.util import (_is_archive_file, ask, get_install_method, + egginfo_to_distinfo) +from packaging.pypi import wrapper +from packaging.version import get_version_predicate +from packaging.database import get_distributions, get_distribution +from packaging.depgraph import generate_graph + +from packaging.errors import (PackagingError, InstallationException, + InstallationConflict, CCompilerError) +from packaging.pypi.errors import ProjectNotFound, ReleaseNotFound + +__all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove', + 'install', 'install_local_project'] + + +def _move_files(files, destination): + """Move the list of files in the destination folder, keeping the same + structure. + + Return a list of tuple (old, new) emplacement of files + + :param files: a list of files to move. + :param destination: the destination directory to put on the files. + if not defined, create a new one, using mkdtemp + """ + if not destination: + destination = tempfile.mkdtemp() + + for old in files: + # not using os.path.join() because basename() might not be + # unique in destination + new = "%s%s" % (destination, old) + + # try to make the paths. + try: + os.makedirs(os.path.dirname(new)) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + raise e + os.rename(old, new) + yield old, new + + +def _run_distutils_install(path): + # backward compat: using setuptools or plain-distutils + cmd = '%s setup.py install --record=%s' + record_file = os.path.join(path, 'RECORD') + os.system(cmd % (sys.executable, record_file)) + if not os.path.exists(record_file): + raise ValueError('failed to install') + else: + egginfo_to_distinfo(record_file, remove_egginfo=True) + + +def _run_setuptools_install(path): + cmd = '%s setup.py install --record=%s --single-version-externally-managed' + record_file = os.path.join(path, 'RECORD') + os.system(cmd % (sys.executable, record_file)) + if not os.path.exists(record_file): + raise ValueError('failed to install') + else: + egginfo_to_distinfo(record_file, remove_egginfo=True) + + +def _run_packaging_install(path): + # XXX check for a valid setup.cfg? + dist = Distribution() + dist.parse_config_files() + try: + dist.run_command('install_dist') + except (IOError, os.error, PackagingError, CCompilerError) as msg: + raise SystemExit("error: " + str(msg)) + + +def _install_dist(dist, path): + """Install a distribution into a path. + + This: + + * unpack the distribution + * copy the files in "path" + * determine if the distribution is packaging or distutils1. + """ + where = dist.unpack() + + if where is None: + raise ValueError('Cannot locate the unpacked archive') + + return _run_install_from_archive(where) + + +def install_local_project(path): + """Install a distribution from a source directory. + + If the source directory contains a setup.py install using distutils1. + If a setup.cfg is found, install using the install_dist command. + + """ + path = os.path.abspath(path) + if os.path.isdir(path): + logger.info('installing from source directory: %s', path) + _run_install_from_dir(path) + elif _is_archive_file(path): + logger.info('installing from archive: %s', path) + _unpacked_dir = tempfile.mkdtemp() + shutil.unpack_archive(path, _unpacked_dir) + _run_install_from_archive(_unpacked_dir) + else: + logger.warning('no projects to install') + + +def _run_install_from_archive(source_dir): + # XXX need a better way + for item in os.listdir(source_dir): + fullpath = os.path.join(source_dir, item) + if os.path.isdir(fullpath): + source_dir = fullpath + break + return _run_install_from_dir(source_dir) + + +install_methods = { + 'packaging': _run_packaging_install, + 'setuptools': _run_setuptools_install, + 'distutils': _run_distutils_install} + + +def _run_install_from_dir(source_dir): + old_dir = os.getcwd() + os.chdir(source_dir) + install_method = get_install_method(source_dir) + func = install_methods[install_method] + try: + func = install_methods[install_method] + return func(source_dir) + finally: + os.chdir(old_dir) + + +def install_dists(dists, path, paths=sys.path): + """Install all distributions provided in dists, with the given prefix. + + If an error occurs while installing one of the distributions, uninstall all + the installed distribution (in the context if this function). + + Return a list of installed dists. + + :param dists: distributions to install + :param path: base path to install distribution in + :param paths: list of paths (defaults to sys.path) to look for info + """ + if not path: + path = tempfile.mkdtemp() + + installed_dists = [] + for dist in dists: + logger.info('installing %s %s', dist.name, dist.version) + try: + _install_dist(dist, path) + installed_dists.append(dist) + except Exception as e: + logger.info('failed: %s', e) + + # reverting + for installed_dist in installed_dists: + logger.info('reverting %s', installed_dist) + _remove_dist(installed_dist, paths) + raise e + return installed_dists + + +def install_from_infos(install_path=None, install=[], remove=[], conflicts=[], + paths=sys.path): + """Install and remove the given distributions. + + The function signature is made to be compatible with the one of get_infos. + The aim of this script is to povide a way to install/remove what's asked, + and to rollback if needed. + + So, it's not possible to be in an inconsistant state, it could be either + installed, either uninstalled, not half-installed. + + The process follow those steps: + + 1. Move all distributions that will be removed in a temporary location + 2. Install all the distributions that will be installed in a temp. loc. + 3. If the installation fails, rollback (eg. move back) those + distributions, or remove what have been installed. + 4. Else, move the distributions to the right locations, and remove for + real the distributions thats need to be removed. + + :param install_path: the installation path where we want to install the + distributions. + :param install: list of distributions that will be installed; install_path + must be provided if this list is not empty. + :param remove: list of distributions that will be removed. + :param conflicts: list of conflicting distributions, eg. that will be in + conflict once the install and remove distribution will be + processed. + :param paths: list of paths (defaults to sys.path) to look for info + """ + # first of all, if we have conflicts, stop here. + if conflicts: + raise InstallationConflict(conflicts) + + if install and not install_path: + raise ValueError("Distributions are to be installed but `install_path`" + " is not provided.") + + # before removing the files, we will start by moving them away + # then, if any error occurs, we could replace them in the good place. + temp_files = {} # contains lists of {dist: (old, new)} paths + temp_dir = None + if remove: + temp_dir = tempfile.mkdtemp() + for dist in remove: + files = dist.list_installed_files() + temp_files[dist] = _move_files(files, temp_dir) + try: + if install: + install_dists(install, install_path, paths) + except: + # if an error occurs, put back the files in the right place. + for files in temp_files.values(): + for old, new in files: + shutil.move(new, old) + if temp_dir: + shutil.rmtree(temp_dir) + # now re-raising + raise + + # we can remove them for good + for files in temp_files.values(): + for old, new in files: + os.remove(new) + if temp_dir: + shutil.rmtree(temp_dir) + + +def _get_setuptools_deps(release): + # NotImplementedError + pass + + +def get_infos(requirements, index=None, installed=None, prefer_final=True): + """Return the informations on what's going to be installed and upgraded. + + :param requirements: is a *string* containing the requirements for this + project (for instance "FooBar 1.1" or "BarBaz (<1.2)") + :param index: If an index is specified, use this one, otherwise, use + :class index.ClientWrapper: to get project metadatas. + :param installed: a list of already installed distributions. + :param prefer_final: when picking up the releases, prefer a "final" one + over a beta/alpha/etc one. + + The results are returned in a dict, containing all the operations + needed to install the given requirements:: + + >>> get_install_info("FooBar (<=1.2)") + {'install': [], 'remove': [], 'conflict': []} + + Conflict contains all the conflicting distributions, if there is a + conflict. + """ + # this function does several things: + # 1. get a release specified by the requirements + # 2. gather its metadata, using setuptools compatibility if needed + # 3. compare this tree with what is currently installed on the system, + # return the requirements of what is missing + # 4. do that recursively and merge back the results + # 5. return a dict containing information about what is needed to install + # or remove + + if not installed: + logger.info('reading installed distributions') + installed = list(get_distributions(use_egg_info=True)) + + infos = {'install': [], 'remove': [], 'conflict': []} + # Is a compatible version of the project already installed ? + predicate = get_version_predicate(requirements) + found = False + + # check that the project isn't already installed + for installed_project in installed: + # is it a compatible project ? + if predicate.name.lower() != installed_project.name.lower(): + continue + found = True + logger.info('found %s %s', installed_project.name, + installed_project.metadata['version']) + + # if we already have something installed, check it matches the + # requirements + if predicate.match(installed_project.metadata['version']): + return infos + break + + if not found: + logger.info('project not installed') + + if not index: + index = wrapper.ClientWrapper() + + if not installed: + installed = get_distributions(use_egg_info=True) + + # Get all the releases that match the requirements + try: + release = index.get_release(requirements) + except (ReleaseNotFound, ProjectNotFound): + raise InstallationException('Release not found: "%s"' % requirements) + + if release is None: + logger.info('could not find a matching project') + return infos + + metadata = release.fetch_metadata() + + # we need to build setuptools deps if any + if 'requires_dist' not in metadata: + metadata['requires_dist'] = _get_setuptools_deps(release) + + # build the dependency graph with local and required dependencies + dists = list(installed) + dists.append(release) + depgraph = generate_graph(dists) + + # Get what the missing deps are + dists = depgraph.missing[release] + if dists: + logger.info("missing dependencies found, retrieving metadata") + # we have missing deps + for dist in dists: + _update_infos(infos, get_infos(dist, index, installed)) + + # Fill in the infos + existing = [d for d in installed if d.name == release.name] + if existing: + infos['remove'].append(existing[0]) + infos['conflict'].extend(depgraph.reverse_list[existing[0]]) + infos['install'].append(release) + return infos + + +def _update_infos(infos, new_infos): + """extends the lists contained in the `info` dict with those contained + in the `new_info` one + """ + for key, value in infos.items(): + if key in new_infos: + infos[key].extend(new_infos[key]) + + +def _remove_dist(dist, paths=sys.path): + remove(dist.name, paths) + + +def remove(project_name, paths=sys.path, auto_confirm=True): + """Removes a single project from the installation""" + dist = get_distribution(project_name, use_egg_info=True, paths=paths) + if dist is None: + raise PackagingError('Distribution "%s" not found' % project_name) + files = dist.list_installed_files(local=True) + rmdirs = [] + rmfiles = [] + tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall') + try: + for file_, md5, size in files: + if os.path.isfile(file_): + dirname, filename = os.path.split(file_) + tmpfile = os.path.join(tmp, filename) + try: + os.rename(file_, tmpfile) + finally: + if not os.path.isfile(file_): + os.rename(tmpfile, file_) + if file_ not in rmfiles: + rmfiles.append(file_) + if dirname not in rmdirs: + rmdirs.append(dirname) + finally: + shutil.rmtree(tmp) + + logger.info('removing %r: ', project_name) + + for file_ in rmfiles: + logger.info(' %s', file_) + + # Taken from the pip project + if auto_confirm: + response = 'y' + else: + response = ask('Proceed (y/n)? ', ('y', 'n')) + + if response == 'y': + file_count = 0 + for file_ in rmfiles: + os.remove(file_) + file_count += 1 + + dir_count = 0 + for dirname in rmdirs: + if not os.path.exists(dirname): + # could + continue + + files_count = 0 + for root, dir, files in os.walk(dirname): + files_count += len(files) + + if files_count > 0: + # XXX Warning + continue + + # empty dirs with only empty dirs + if os.stat(dirname).st_mode & stat.S_IWUSR: + # XXX Add a callable in shutil.rmtree to count + # the number of deleted elements + shutil.rmtree(dirname) + dir_count += 1 + + # removing the top path + # XXX count it ? + if os.path.exists(dist.path): + shutil.rmtree(dist.path) + + logger.info('success: removed %d files and %d dirs', + file_count, dir_count) + + +def install(project): + logger.info('getting information about %r', project) + try: + info = get_infos(project) + except InstallationException: + logger.info('cound not find %r', project) + return + + if info['install'] == []: + logger.info('nothing to install') + return + + install_path = get_config_var('base') + try: + install_from_infos(install_path, + info['install'], info['remove'], info['conflict']) + + except InstallationConflict as e: + if logger.isEnabledFor(logging.INFO): + projects = ['%s %s' % (p.name, p.version) for p in e.args[0]] + logger.info('%r conflicts with %s', project, ','.join(projects)) + + +def _main(**attrs): + if 'script_args' not in attrs: + import sys + attrs['requirements'] = sys.argv[1] + get_infos(**attrs) + +if __name__ == '__main__': + _main() diff --git a/Lib/packaging/manifest.py b/Lib/packaging/manifest.py new file mode 100644 index 0000000000..a3798530a5 --- /dev/null +++ b/Lib/packaging/manifest.py @@ -0,0 +1,372 @@ +"""Class representing the list of files in a distribution. + +The Manifest class can be used to: + + - read or write a MANIFEST file + - read a template file and find out the file list +""" +# XXX todo: document + add tests +import re +import os +import fnmatch + +from packaging import logger +from packaging.util import write_file, convert_path +from packaging.errors import (PackagingTemplateError, + PackagingInternalError) + +__all__ = ['Manifest'] + +# a \ followed by some spaces + EOL +_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M) +_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S) + + +class Manifest(object): + """A list of files built by on exploring the filesystem and filtered by + applying various patterns to what we find there. + """ + + def __init__(self): + self.allfiles = None + self.files = [] + + # + # Public API + # + + def findall(self, dir=os.curdir): + self.allfiles = _findall(dir) + + def append(self, item): + self.files.append(item) + + def extend(self, items): + self.files.extend(items) + + def sort(self): + # Not a strict lexical sort! + self.files = [os.path.join(*path_tuple) for path_tuple in + sorted(os.path.split(path) for path in self.files)] + + def clear(self): + """Clear all collected files.""" + self.files = [] + if self.allfiles is not None: + self.allfiles = [] + + def remove_duplicates(self): + # Assumes list has been sorted! + for i in range(len(self.files) - 1, 0, -1): + if self.files[i] == self.files[i - 1]: + del self.files[i] + + def read_template(self, path_or_file): + """Read and parse a manifest template file. + 'path' can be a path or a file-like object. + + Updates the list accordingly. + """ + if isinstance(path_or_file, str): + f = open(path_or_file) + else: + f = path_or_file + + try: + content = f.read() + # first, let's unwrap collapsed lines + content = _COLLAPSE_PATTERN.sub('', content) + # next, let's remove commented lines and empty lines + content = _COMMENTED_LINE.sub('', content) + + # now we have our cleaned up lines + lines = [line.strip() for line in content.split('\n')] + finally: + f.close() + + for line in lines: + if line == '': + continue + try: + self._process_template_line(line) + except PackagingTemplateError as msg: + logger.warning("%s, %s", path_or_file, msg) + + def write(self, path): + """Write the file list in 'self.filelist' (presumably as filled in + by 'add_defaults()' and 'read_template()') to the manifest file + named by 'self.manifest'. + """ + if os.path.isfile(path): + with open(path) as fp: + first_line = fp.readline() + + if first_line != '# file GENERATED by packaging, do NOT edit\n': + logger.info("not writing to manually maintained " + "manifest file %r", path) + return + + self.sort() + self.remove_duplicates() + content = self.files[:] + content.insert(0, '# file GENERATED by packaging, do NOT edit') + logger.info("writing manifest file %r", path) + write_file(path, content) + + def read(self, path): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + logger.info("reading manifest file %r", path) + with open(path) as manifest: + for line in manifest.readlines(): + self.append(line) + + def exclude_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Remove strings (presumably filenames) from 'files' that match + 'pattern'. + + Other parameters are the same as for 'include_pattern()', above. + The list 'self.files' is modified in place. Return True if files are + found. + """ + files_found = False + pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex) + for i in range(len(self.files) - 1, -1, -1): + if pattern_re.search(self.files[i]): + del self.files[i] + files_found = True + + return files_found + + # + # Private API + # + + def _parse_template_line(self, line): + words = line.split() + if len(words) == 1: + # no action given, let's use the default 'include' + words.insert(0, 'include') + + action = words[0] + patterns = dir = dir_pattern = None + + if action in ('include', 'exclude', + 'global-include', 'global-exclude'): + if len(words) < 2: + raise PackagingTemplateError( + "%r expects ..." % action) + + patterns = [convert_path(word) for word in words[1:]] + + elif action in ('recursive-include', 'recursive-exclude'): + if len(words) < 3: + raise PackagingTemplateError( + "%r expects

..." % action) + + dir = convert_path(words[1]) + patterns = [convert_path(word) for word in words[2:]] + + elif action in ('graft', 'prune'): + if len(words) != 2: + raise PackagingTemplateError( + "%r expects a single " % action) + + dir_pattern = convert_path(words[1]) + + else: + raise PackagingTemplateError("unknown action %r" % action) + + return action, patterns, dir, dir_pattern + + def _process_template_line(self, line): + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dir_pattern). + action, patterns, dir, dir_pattern = self._parse_template_line(line) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=True): + logger.warning("no files found matching %r", pattern) + + elif action == 'exclude': + for pattern in patterns: + if not self.exclude_pattern(pattern, anchor=True): + logger.warning("no previously-included files " + "found matching %r", pattern) + + elif action == 'global-include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=False): + logger.warning("no files found matching %r " + "anywhere in distribution", pattern) + + elif action == 'global-exclude': + for pattern in patterns: + if not self.exclude_pattern(pattern, anchor=False): + logger.warning("no previously-included files " + "matching %r found anywhere in " + "distribution", pattern) + + elif action == 'recursive-include': + for pattern in patterns: + if not self._include_pattern(pattern, prefix=dir): + logger.warning("no files found matching %r " + "under directory %r", pattern, dir) + + elif action == 'recursive-exclude': + for pattern in patterns: + if not self.exclude_pattern(pattern, prefix=dir): + logger.warning("no previously-included files " + "matching %r found under directory %r", + pattern, dir) + + elif action == 'graft': + if not self._include_pattern(None, prefix=dir_pattern): + logger.warning("no directories found matching %r", + dir_pattern) + + elif action == 'prune': + if not self.exclude_pattern(None, prefix=dir_pattern): + logger.warning("no previously-included directories found " + "matching %r", dir_pattern) + else: + raise PackagingInternalError( + "this cannot happen: invalid action %r" % action) + + def _include_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Select strings (presumably filenames) from 'self.files' that + match 'pattern', a Unix-style wildcard (glob) pattern. + + Patterns are not quite the same as implemented by the 'fnmatch' + module: '*' and '?' match non-special characters, where "special" + is platform-dependent: slash on Unix; colon, slash, and backslash on + DOS/Windows; and colon on Mac OS. + + If 'anchor' is true (the default), then the pattern match is more + stringent: "*.py" will match "foo.py" but not "foo/bar.py". If + 'anchor' is false, both of these will match. + + If 'prefix' is supplied, then only filenames starting with 'prefix' + (itself a pattern) and ending with 'pattern', with anything in between + them, will match. 'anchor' is ignored in this case. + + If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and + 'pattern' is assumed to be either a string containing a regex or a + regex object -- no translation is done, the regex is just compiled + and used as-is. + + Selected strings will be added to self.files. + + Return True if files are found. + """ + files_found = False + pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex) + + # delayed loading of allfiles list + if self.allfiles is None: + self.findall() + + for name in self.allfiles: + if pattern_re.search(name): + self.files.append(name) + files_found = True + + return files_found + + +# +# Utility functions +# +def _findall(dir=os.curdir): + """Find all files under 'dir' and return the list of full filenames + (relative to 'dir'). + """ + from stat import S_ISREG, S_ISDIR, S_ISLNK + + list = [] + stack = [dir] + pop = stack.pop + push = stack.append + + while stack: + dir = pop() + names = os.listdir(dir) + + for name in names: + if dir != os.curdir: # avoid the dreaded "./" syndrome + fullname = os.path.join(dir, name) + else: + fullname = name + + # Avoid excess stat calls -- just one will do, thank you! + stat = os.stat(fullname) + mode = stat.st_mode + if S_ISREG(mode): + list.append(fullname) + elif S_ISDIR(mode) and not S_ISLNK(mode): + push(fullname) + + return list + + +def _glob_to_re(pattern): + """Translate a shell-like glob pattern to a regular expression. + + Return a string containing the regex. Differs from + 'fnmatch.translate()' in that '*' does not match "special characters" + (which are platform-specific). + """ + pattern_re = fnmatch.translate(pattern) + + # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which + # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, + # and by extension they shouldn't match such "special characters" under + # any OS. So change all non-escaped dots in the RE to match any + # character except the special characters. + # XXX currently the "special characters" are just slash -- i.e. this is + # Unix-only. + pattern_re = re.sub(r'((?': lambda x, y: x > y, + '>=': lambda x, y: x >= y, + '<': lambda x, y: x < y, + '<=': lambda x, y: x <= y, + 'in': lambda x, y: x in y, + 'not in': lambda x, y: x not in y} + + +def _operate(operation, x, y): + return _OPERATORS[operation](x, y) + + +# restricted set of variables +_VARS = {'sys.platform': sys.platform, + 'python_version': sys.version[:3], + 'python_full_version': sys.version.split(' ', 1)[0], + 'os.name': os.name, + 'platform.version': platform.version(), + 'platform.machine': platform.machine(), + 'platform.python_implementation': platform.python_implementation()} + + +class _Operation: + + def __init__(self, execution_context=None): + self.left = None + self.op = None + self.right = None + if execution_context is None: + execution_context = {} + self.execution_context = execution_context + + def _get_var(self, name): + if name in self.execution_context: + return self.execution_context[name] + return _VARS[name] + + def __repr__(self): + return '%s %s %s' % (self.left, self.op, self.right) + + def _is_string(self, value): + if value is None or len(value) < 2: + return False + for delimiter in '"\'': + if value[0] == value[-1] == delimiter: + return True + return False + + def _is_name(self, value): + return value in _VARS + + def _convert(self, value): + if value in _VARS: + return self._get_var(value) + return value.strip('"\'') + + def _check_name(self, value): + if value not in _VARS: + raise NameError(value) + + def _nonsense_op(self): + msg = 'This operation is not supported : "%s"' % self + raise SyntaxError(msg) + + def __call__(self): + # make sure we do something useful + if self._is_string(self.left): + if self._is_string(self.right): + self._nonsense_op() + self._check_name(self.right) + else: + if not self._is_string(self.right): + self._nonsense_op() + self._check_name(self.left) + + if self.op not in _OPERATORS: + raise TypeError('Operator not supported "%s"' % self.op) + + left = self._convert(self.left) + right = self._convert(self.right) + return _operate(self.op, left, right) + + +class _OR: + def __init__(self, left, right=None): + self.left = left + self.right = right + + def filled(self): + return self.right is not None + + def __repr__(self): + return 'OR(%r, %r)' % (self.left, self.right) + + def __call__(self): + return self.left() or self.right() + + +class _AND: + def __init__(self, left, right=None): + self.left = left + self.right = right + + def filled(self): + return self.right is not None + + def __repr__(self): + return 'AND(%r, %r)' % (self.left, self.right) + + def __call__(self): + return self.left() and self.right() + + +def interpret(marker, execution_context=None): + """Interpret a marker and return a result depending on environment.""" + marker = marker.strip().encode() + ops = [] + op_starting = True + for token in tokenize(BytesIO(marker).readline): + # Unpack token + toktype, tokval, rowcol, line, logical_line = token + if toktype not in (NAME, OP, STRING, ENDMARKER, ENCODING): + raise SyntaxError('Type not supported "%s"' % tokval) + + if op_starting: + op = _Operation(execution_context) + if len(ops) > 0: + last = ops[-1] + if isinstance(last, (_OR, _AND)) and not last.filled(): + last.right = op + else: + ops.append(op) + else: + ops.append(op) + op_starting = False + else: + op = ops[-1] + + if (toktype == ENDMARKER or + (toktype == NAME and tokval in ('and', 'or'))): + if toktype == NAME and tokval == 'and': + ops.append(_AND(ops.pop())) + elif toktype == NAME and tokval == 'or': + ops.append(_OR(ops.pop())) + op_starting = True + continue + + if isinstance(op, (_OR, _AND)) and op.right is not None: + op = op.right + + if ((toktype in (NAME, STRING) and tokval not in ('in', 'not')) + or (toktype == OP and tokval == '.')): + if op.op is None: + if op.left is None: + op.left = tokval + else: + op.left += tokval + else: + if op.right is None: + op.right = tokval + else: + op.right += tokval + elif toktype == OP or tokval in ('in', 'not'): + if tokval == 'in' and op.op == 'not': + op.op = 'not in' + else: + op.op = tokval + + for op in ops: + if not op(): + return False + return True diff --git a/Lib/packaging/metadata.py b/Lib/packaging/metadata.py new file mode 100644 index 0000000000..8abbe384a7 --- /dev/null +++ b/Lib/packaging/metadata.py @@ -0,0 +1,552 @@ +"""Implementation of the Metadata for Python packages PEPs. + +Supports all metadata formats (1.0, 1.1, 1.2). +""" + +import re +import logging + +from io import StringIO +from email import message_from_file +from packaging import logger +from packaging.markers import interpret +from packaging.version import (is_valid_predicate, is_valid_version, + is_valid_versions) +from packaging.errors import (MetadataMissingError, + MetadataConflictError, + MetadataUnrecognizedVersionError) + +try: + # docutils is installed + from docutils.utils import Reporter + from docutils.parsers.rst import Parser + from docutils import frontend + from docutils import nodes + + class SilentReporter(Reporter): + + def __init__(self, source, report_level, halt_level, stream=None, + debug=0, encoding='ascii', error_handler='replace'): + self.messages = [] + Reporter.__init__(self, source, report_level, halt_level, stream, + debug, encoding, error_handler) + + def system_message(self, level, message, *children, **kwargs): + self.messages.append((level, message, children, kwargs)) + + _HAS_DOCUTILS = True +except ImportError: + # docutils is not installed + _HAS_DOCUTILS = False + +# public API of this module +__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION'] + +# Encoding used for the PKG-INFO files +PKG_INFO_ENCODING = 'utf-8' + +# preferred version. Hopefully will be changed +# to 1.2 once PEP 345 is supported everywhere +PKG_INFO_PREFERRED_VERSION = '1.0' + +_LINE_PREFIX = re.compile('\n \|') +_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'License') + +_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'License', 'Classifier', 'Download-URL', 'Obsoletes', + 'Provides', 'Requires') + +_314_MARKERS = ('Obsoletes', 'Provides', 'Requires') + +_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'Maintainer', 'Maintainer-email', 'License', + 'Classifier', 'Download-URL', 'Obsoletes-Dist', + 'Project-URL', 'Provides-Dist', 'Requires-Dist', + 'Requires-Python', 'Requires-External') + +_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', + 'Obsoletes-Dist', 'Requires-External', 'Maintainer', + 'Maintainer-email', 'Project-URL') + +_ALL_FIELDS = set() +_ALL_FIELDS.update(_241_FIELDS) +_ALL_FIELDS.update(_314_FIELDS) +_ALL_FIELDS.update(_345_FIELDS) + + +def _version2fieldlist(version): + if version == '1.0': + return _241_FIELDS + elif version == '1.1': + return _314_FIELDS + elif version == '1.2': + return _345_FIELDS + raise MetadataUnrecognizedVersionError(version) + + +def _best_version(fields): + """Detect the best version depending on the fields used.""" + def _has_marker(keys, markers): + for marker in markers: + if marker in keys: + return True + return False + + keys = list(fields) + possible_versions = ['1.0', '1.1', '1.2'] + + # first let's try to see if a field is not part of one of the version + for key in keys: + if key not in _241_FIELDS and '1.0' in possible_versions: + possible_versions.remove('1.0') + if key not in _314_FIELDS and '1.1' in possible_versions: + possible_versions.remove('1.1') + if key not in _345_FIELDS and '1.2' in possible_versions: + possible_versions.remove('1.2') + + # possible_version contains qualified versions + if len(possible_versions) == 1: + return possible_versions[0] # found ! + elif len(possible_versions) == 0: + raise MetadataConflictError('Unknown metadata set') + + # let's see if one unique marker is found + is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS) + is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS) + if is_1_1 and is_1_2: + raise MetadataConflictError('You used incompatible 1.1 and 1.2 fields') + + # we have the choice, either 1.0, or 1.2 + # - 1.0 has a broken Summary field but works with all tools + # - 1.1 is to avoid + # - 1.2 fixes Summary but is not widespread yet + if not is_1_1 and not is_1_2: + # we couldn't find any specific marker + if PKG_INFO_PREFERRED_VERSION in possible_versions: + return PKG_INFO_PREFERRED_VERSION + if is_1_1: + return '1.1' + + # default marker when 1.0 is disqualified + return '1.2' + + +_ATTR2FIELD = { + 'metadata_version': 'Metadata-Version', + 'name': 'Name', + 'version': 'Version', + 'platform': 'Platform', + 'supported_platform': 'Supported-Platform', + 'summary': 'Summary', + 'description': 'Description', + 'keywords': 'Keywords', + 'home_page': 'Home-page', + 'author': 'Author', + 'author_email': 'Author-email', + 'maintainer': 'Maintainer', + 'maintainer_email': 'Maintainer-email', + 'license': 'License', + 'classifier': 'Classifier', + 'download_url': 'Download-URL', + 'obsoletes_dist': 'Obsoletes-Dist', + 'provides_dist': 'Provides-Dist', + 'requires_dist': 'Requires-Dist', + 'requires_python': 'Requires-Python', + 'requires_external': 'Requires-External', + 'requires': 'Requires', + 'provides': 'Provides', + 'obsoletes': 'Obsoletes', + 'project_url': 'Project-URL', +} + +_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') +_VERSIONS_FIELDS = ('Requires-Python',) +_VERSION_FIELDS = ('Version',) +_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', + 'Requires', 'Provides', 'Obsoletes-Dist', + 'Provides-Dist', 'Requires-Dist', 'Requires-External', + 'Project-URL', 'Supported-Platform') +_LISTTUPLEFIELDS = ('Project-URL',) + +_ELEMENTSFIELD = ('Keywords',) + +_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') + +_MISSING = object() + + +class NoDefault: + """Marker object used for clean representation""" + def __repr__(self): + return '' + +_MISSING = NoDefault() + + +class Metadata: + """The metadata of a release. + + Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can + instantiate the class with one of these arguments (or none): + - *path*, the path to a METADATA file + - *fileobj* give a file-like object with METADATA as content + - *mapping* is a dict-like object + """ + # TODO document that execution_context and platform_dependent are used + # to filter on query, not when setting a key + # also document the mapping API and UNKNOWN default key + + def __init__(self, path=None, platform_dependent=False, + execution_context=None, fileobj=None, mapping=None): + self._fields = {} + self.requires_files = [] + self.docutils_support = _HAS_DOCUTILS + self.platform_dependent = platform_dependent + self.execution_context = execution_context + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + if path is not None: + self.read(path) + elif fileobj is not None: + self.read_file(fileobj) + elif mapping is not None: + self.update(mapping) + + def _set_best_version(self): + self._fields['Metadata-Version'] = _best_version(self._fields) + + def _write_field(self, file, name, value): + file.write('%s: %s\n' % (name, value)) + + def __getitem__(self, name): + return self.get(name) + + def __setitem__(self, name, value): + return self.set(name, value) + + def __delitem__(self, name): + field_name = self._convert_name(name) + try: + del self._fields[field_name] + except KeyError: + raise KeyError(name) + self._set_best_version() + + def __contains__(self, name): + return (name in self._fields or + self._convert_name(name) in self._fields) + + def _convert_name(self, name): + if name in _ALL_FIELDS: + return name + name = name.replace('-', '_').lower() + return _ATTR2FIELD.get(name, name) + + def _default_value(self, name): + if name in _LISTFIELDS or name in _ELEMENTSFIELD: + return [] + return 'UNKNOWN' + + def _check_rst_data(self, data): + """Return warnings when the provided data has syntax errors.""" + source_path = StringIO() + parser = Parser() + settings = frontend.OptionParser().get_default_values() + settings.tab_width = 4 + settings.pep_references = None + settings.rfc_references = None + reporter = SilentReporter(source_path, + settings.report_level, + settings.halt_level, + stream=settings.warning_stream, + debug=settings.debug, + encoding=settings.error_encoding, + error_handler=settings.error_encoding_error_handler) + + document = nodes.document(settings, reporter, source=source_path) + document.note_source(source_path, -1) + try: + parser.parse(data, document) + except AttributeError: + reporter.messages.append((-1, 'Could not finish the parsing.', + '', {})) + + return reporter.messages + + def _platform(self, value): + if not self.platform_dependent or ';' not in value: + return True, value + value, marker = value.split(';') + return interpret(marker, self.execution_context), value + + def _remove_line_prefix(self, value): + return _LINE_PREFIX.sub('\n', value) + + # + # Public API + # + def get_fullname(self): + """Return the distribution name with version""" + return '%s-%s' % (self['Name'], self['Version']) + + def is_metadata_field(self, name): + """return True if name is a valid metadata key""" + name = self._convert_name(name) + return name in _ALL_FIELDS + + def is_multi_field(self, name): + name = self._convert_name(name) + return name in _LISTFIELDS + + def read(self, filepath): + """Read the metadata values from a file path.""" + with open(filepath, 'r', encoding='ascii') as fp: + self.read_file(fp) + + def read_file(self, fileob): + """Read the metadata values from a file object.""" + msg = message_from_file(fileob) + self._fields['Metadata-Version'] = msg['metadata-version'] + + for field in _version2fieldlist(self['Metadata-Version']): + if field in _LISTFIELDS: + # we can have multiple lines + values = msg.get_all(field) + if field in _LISTTUPLEFIELDS and values is not None: + values = [tuple(value.split(',')) for value in values] + self.set(field, values) + else: + # single line + value = msg[field] + if value is not None and value != 'UNKNOWN': + self.set(field, value) + + def write(self, filepath): + """Write the metadata fields to filepath.""" + with open(filepath, 'w') as fp: + self.write_file(fp) + + def write_file(self, fileobject): + """Write the PKG-INFO format data to a file object.""" + self._set_best_version() + for field in _version2fieldlist(self['Metadata-Version']): + values = self.get(field) + if field in _ELEMENTSFIELD: + self._write_field(fileobject, field, ','.join(values)) + continue + if field not in _LISTFIELDS: + if field == 'Description': + values = values.replace('\n', '\n |') + values = [values] + + if field in _LISTTUPLEFIELDS: + values = [','.join(value) for value in values] + + for value in values: + self._write_field(fileobject, field, value) + + def update(self, other=None, **kwargs): + """Set metadata values from the given iterable `other` and kwargs. + + Behavior is like `dict.update`: If `other` has a ``keys`` method, + they are looped over and ``self[key]`` is assigned ``other[key]``. + Else, ``other`` is an iterable of ``(key, value)`` iterables. + + Keys that don't match a metadata field or that have an empty value are + dropped. + """ + def _set(key, value): + if key in _ATTR2FIELD and value: + self.set(self._convert_name(key), value) + + if other is None: + pass + elif hasattr(other, 'keys'): + for k in other.keys(): + _set(k, other[k]) + else: + for k, v in other: + _set(k, v) + + if kwargs: + self.update(kwargs) + + def set(self, name, value): + """Control then set a metadata field.""" + name = self._convert_name(name) + + if ((name in _ELEMENTSFIELD or name == 'Platform') and + not isinstance(value, (list, tuple))): + if isinstance(value, str): + value = [v.strip() for v in value.split(',')] + else: + value = [] + elif (name in _LISTFIELDS and + not isinstance(value, (list, tuple))): + if isinstance(value, str): + value = [value] + else: + value = [] + + if logger.isEnabledFor(logging.WARNING): + if name in _PREDICATE_FIELDS and value is not None: + for v in value: + # check that the values are valid predicates + if not is_valid_predicate(v.split(';')[0]): + logger.warning( + '%r is not a valid predicate (field %r)', + v, name) + # FIXME this rejects UNKNOWN, is that right? + elif name in _VERSIONS_FIELDS and value is not None: + if not is_valid_versions(value): + logger.warning('%r is not a valid version (field %r)', + value, name) + elif name in _VERSION_FIELDS and value is not None: + if not is_valid_version(value): + logger.warning('%r is not a valid version (field %r)', + value, name) + + if name in _UNICODEFIELDS: + if name == 'Description': + value = self._remove_line_prefix(value) + + self._fields[name] = value + self._set_best_version() + + def get(self, name, default=_MISSING): + """Get a metadata field.""" + name = self._convert_name(name) + if name not in self._fields: + if default is _MISSING: + default = self._default_value(name) + return default + if name in _UNICODEFIELDS: + value = self._fields[name] + return value + elif name in _LISTFIELDS: + value = self._fields[name] + if value is None: + return [] + res = [] + for val in value: + valid, val = self._platform(val) + if not valid: + continue + if name not in _LISTTUPLEFIELDS: + res.append(val) + else: + # That's for Project-URL + res.append((val[0], val[1])) + return res + + elif name in _ELEMENTSFIELD: + valid, value = self._platform(self._fields[name]) + if not valid: + return [] + if isinstance(value, str): + return value.split(',') + valid, value = self._platform(self._fields[name]) + if not valid: + return None + return value + + def check(self, strict=False, restructuredtext=False): + """Check if the metadata is compliant. If strict is False then raise if + no Name or Version are provided""" + # XXX should check the versions (if the file was loaded) + missing, warnings = [], [] + + for attr in ('Name', 'Version'): # required by PEP 345 + if attr not in self: + missing.append(attr) + + if strict and missing != []: + msg = 'missing required metadata: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + + for attr in ('Home-page', 'Author'): + if attr not in self: + missing.append(attr) + + if _HAS_DOCUTILS and restructuredtext: + warnings.extend(self._check_rst_data(self['Description'])) + + # checking metadata 1.2 (XXX needs to check 1.1, 1.0) + if self['Metadata-Version'] != '1.2': + return missing, warnings + + def is_valid_predicates(value): + for v in value: + if not is_valid_predicate(v.split(';')[0]): + return False + return True + + for fields, controller in ((_PREDICATE_FIELDS, is_valid_predicates), + (_VERSIONS_FIELDS, is_valid_versions), + (_VERSION_FIELDS, is_valid_version)): + for field in fields: + value = self.get(field, None) + if value is not None and not controller(value): + warnings.append('Wrong value for %r: %s' % (field, value)) + + return missing, warnings + + def todict(self): + """Return fields as a dict. + + Field names will be converted to use the underscore-lowercase style + instead of hyphen-mixed case (i.e. home_page instead of Home-page). + """ + data = { + 'metadata_version': self['Metadata-Version'], + 'name': self['Name'], + 'version': self['Version'], + 'summary': self['Summary'], + 'home_page': self['Home-page'], + 'author': self['Author'], + 'author_email': self['Author-email'], + 'license': self['License'], + 'description': self['Description'], + 'keywords': self['Keywords'], + 'platform': self['Platform'], + 'classifier': self['Classifier'], + 'download_url': self['Download-URL'], + } + + if self['Metadata-Version'] == '1.2': + data['requires_dist'] = self['Requires-Dist'] + data['requires_python'] = self['Requires-Python'] + data['requires_external'] = self['Requires-External'] + data['provides_dist'] = self['Provides-Dist'] + data['obsoletes_dist'] = self['Obsoletes-Dist'] + data['project_url'] = [','.join(url) for url in + self['Project-URL']] + + elif self['Metadata-Version'] == '1.1': + data['provides'] = self['Provides'] + data['requires'] = self['Requires'] + data['obsoletes'] = self['Obsoletes'] + + return data + + # Mapping API + + def keys(self): + return _version2fieldlist(self['Metadata-Version']) + + def __iter__(self): + for key in self.keys(): + yield key + + def values(self): + return [self[key] for key in list(self.keys())] + + def items(self): + return [(key, self[key]) for key in list(self.keys())] diff --git a/Lib/packaging/pypi/__init__.py b/Lib/packaging/pypi/__init__.py new file mode 100644 index 0000000000..5660c50ade --- /dev/null +++ b/Lib/packaging/pypi/__init__.py @@ -0,0 +1,9 @@ +"""Low-level and high-level APIs to interact with project indexes.""" + +__all__ = ['simple', + 'xmlrpc', + 'dist', + 'errors', + 'mirrors'] + +from packaging.pypi.dist import ReleaseInfo, ReleasesList, DistInfo diff --git a/Lib/packaging/pypi/base.py b/Lib/packaging/pypi/base.py new file mode 100644 index 0000000000..305fca9cc8 --- /dev/null +++ b/Lib/packaging/pypi/base.py @@ -0,0 +1,48 @@ +"""Base class for index crawlers.""" + +from packaging.pypi.dist import ReleasesList + + +class BaseClient: + """Base class containing common methods for the index crawlers/clients""" + + def __init__(self, prefer_final, prefer_source): + self._prefer_final = prefer_final + self._prefer_source = prefer_source + self._index = self + + def _get_prefer_final(self, prefer_final=None): + """Return the prefer_final internal parameter or the specified one if + provided""" + if prefer_final: + return prefer_final + else: + return self._prefer_final + + def _get_prefer_source(self, prefer_source=None): + """Return the prefer_source internal parameter or the specified one if + provided""" + if prefer_source: + return prefer_source + else: + return self._prefer_source + + def _get_project(self, project_name): + """Return an project instance, create it if necessary""" + return self._projects.setdefault(project_name.lower(), + ReleasesList(project_name, index=self._index)) + + def download_distribution(self, requirements, temp_path=None, + prefer_source=None, prefer_final=None): + """Download a distribution from the last release according to the + requirements. + + If temp_path is provided, download to this path, otherwise, create a + temporary location for the download and return it. + """ + prefer_final = self._get_prefer_final(prefer_final) + prefer_source = self._get_prefer_source(prefer_source) + release = self.get_release(requirements, prefer_final) + if release: + dist = release.get_distribution(prefer_source=prefer_source) + return dist.download(temp_path) diff --git a/Lib/packaging/pypi/dist.py b/Lib/packaging/pypi/dist.py new file mode 100644 index 0000000000..16510dffd8 --- /dev/null +++ b/Lib/packaging/pypi/dist.py @@ -0,0 +1,547 @@ +"""Classes representing releases and distributions retrieved from indexes. + +A project (= unique name) can have several releases (= versions) and +each release can have several distributions (= sdist and bdists). + +Release objects contain metadata-related information (see PEP 376); +distribution objects contain download-related information. +""" + +import sys +import mimetypes +import re +import tempfile +import urllib.request +import urllib.parse +import urllib.error +import urllib.parse +import hashlib +from shutil import unpack_archive + +from packaging.errors import IrrationalVersionError +from packaging.version import (suggest_normalized_version, NormalizedVersion, + get_version_predicate) +from packaging.metadata import Metadata +from packaging.pypi.errors import (HashDoesNotMatch, UnsupportedHashName, + CantParseArchiveName) + + +__all__ = ['ReleaseInfo', 'DistInfo', 'ReleasesList', 'get_infos_from_url'] + +EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz .egg".split() +MD5_HASH = re.compile(r'^.*#md5=([a-f0-9]+)$') +DIST_TYPES = ['bdist', 'sdist'] + + +class IndexReference: + """Mixin used to store the index reference""" + def set_index(self, index=None): + self._index = index + + +class ReleaseInfo(IndexReference): + """Represent a release of a project (a project with a specific version). + The release contain the _metadata informations related to this specific + version, and is also a container for distribution related informations. + + See the DistInfo class for more information about distributions. + """ + + def __init__(self, name, version, metadata=None, hidden=False, + index=None, **kwargs): + """ + :param name: the name of the distribution + :param version: the version of the distribution + :param metadata: the metadata fields of the release. + :type metadata: dict + :param kwargs: optional arguments for a new distribution. + """ + self.set_index(index) + self.name = name + self._version = None + self.version = version + if metadata: + self.metadata = Metadata(mapping=metadata) + else: + self.metadata = None + self.dists = {} + self.hidden = hidden + + if 'dist_type' in kwargs: + dist_type = kwargs.pop('dist_type') + self.add_distribution(dist_type, **kwargs) + + def set_version(self, version): + try: + self._version = NormalizedVersion(version) + except IrrationalVersionError: + suggestion = suggest_normalized_version(version) + if suggestion: + self.version = suggestion + else: + raise IrrationalVersionError(version) + + def get_version(self): + return self._version + + version = property(get_version, set_version) + + def fetch_metadata(self): + """If the metadata is not set, use the indexes to get it""" + if not self.metadata: + self._index.get_metadata(self.name, str(self.version)) + return self.metadata + + @property + def is_final(self): + """proxy to version.is_final""" + return self.version.is_final + + def fetch_distributions(self): + if self.dists is None: + self._index.get_distributions(self.name, str(self.version)) + if self.dists is None: + self.dists = {} + return self.dists + + def add_distribution(self, dist_type='sdist', python_version=None, + **params): + """Add distribution informations to this release. + If distribution information is already set for this distribution type, + add the given url paths to the distribution. This can be useful while + some of them fails to download. + + :param dist_type: the distribution type (eg. "sdist", "bdist", etc.) + :param params: the fields to be passed to the distribution object + (see the :class:DistInfo constructor). + """ + if dist_type not in DIST_TYPES: + raise ValueError(dist_type) + if dist_type in self.dists: + self.dists[dist_type].add_url(**params) + else: + self.dists[dist_type] = DistInfo(self, dist_type, + index=self._index, **params) + if python_version: + self.dists[dist_type].python_version = python_version + + def get_distribution(self, dist_type=None, prefer_source=True): + """Return a distribution. + + If dist_type is set, find first for this distribution type, and just + act as an alias of __get_item__. + + If prefer_source is True, search first for source distribution, and if + not return one existing distribution. + """ + if len(self.dists) == 0: + raise LookupError() + if dist_type: + return self[dist_type] + if prefer_source: + if "sdist" in self.dists: + dist = self["sdist"] + else: + dist = next(self.dists.values()) + return dist + + def unpack(self, path=None, prefer_source=True): + """Unpack the distribution to the given path. + + If not destination is given, creates a temporary location. + + Returns the location of the extracted files (root). + """ + return self.get_distribution(prefer_source=prefer_source)\ + .unpack(path=path) + + def download(self, temp_path=None, prefer_source=True): + """Download the distribution, using the requirements. + + If more than one distribution match the requirements, use the last + version. + Download the distribution, and put it in the temp_path. If no temp_path + is given, creates and return one. + + Returns the complete absolute path to the downloaded archive. + """ + return self.get_distribution(prefer_source=prefer_source)\ + .download(path=temp_path) + + def set_metadata(self, metadata): + if not self.metadata: + self.metadata = Metadata() + self.metadata.update(metadata) + + def __getitem__(self, item): + """distributions are available using release["sdist"]""" + return self.dists[item] + + def _check_is_comparable(self, other): + if not isinstance(other, ReleaseInfo): + raise TypeError("cannot compare %s and %s" + % (type(self).__name__, type(other).__name__)) + elif self.name != other.name: + raise TypeError("cannot compare %s and %s" + % (self.name, other.name)) + + def __repr__(self): + return "<%s %s>" % (self.name, self.version) + + def __eq__(self, other): + self._check_is_comparable(other) + return self.version == other.version + + def __lt__(self, other): + self._check_is_comparable(other) + return self.version < other.version + + def __ne__(self, other): + return not self.__eq__(other) + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__eq__(other) or self.__lt__(other) + + def __ge__(self, other): + return self.__eq__(other) or self.__gt__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +class DistInfo(IndexReference): + """Represents a distribution retrieved from an index (sdist, bdist, ...) + """ + + def __init__(self, release, dist_type=None, url=None, hashname=None, + hashval=None, is_external=True, python_version=None, + index=None): + """Create a new instance of DistInfo. + + :param release: a DistInfo class is relative to a release. + :param dist_type: the type of the dist (eg. source, bin-*, etc.) + :param url: URL where we found this distribution + :param hashname: the name of the hash we want to use. Refer to the + hashlib.new documentation for more information. + :param hashval: the hash value. + :param is_external: we need to know if the provided url comes from + an index browsing, or from an external resource. + + """ + self.set_index(index) + self.release = release + self.dist_type = dist_type + self.python_version = python_version + self._unpacked_dir = None + # set the downloaded path to None by default. The goal here + # is to not download distributions multiple times + self.downloaded_location = None + # We store urls in dict, because we need to have a bit more infos + # than the simple URL. It will be used later to find the good url to + # use. + # We have two _url* attributes: _url and urls. urls contains a list + # of dict for the different urls, and _url contains the choosen url, in + # order to dont make the selection process multiple times. + self.urls = [] + self._url = None + self.add_url(url, hashname, hashval, is_external) + + def add_url(self, url=None, hashname=None, hashval=None, is_external=True): + """Add a new url to the list of urls""" + if hashname is not None: + try: + hashlib.new(hashname) + except ValueError: + raise UnsupportedHashName(hashname) + if not url in [u['url'] for u in self.urls]: + self.urls.append({ + 'url': url, + 'hashname': hashname, + 'hashval': hashval, + 'is_external': is_external, + }) + # reset the url selection process + self._url = None + + @property + def url(self): + """Pick up the right url for the list of urls in self.urls""" + # We return internal urls over externals. + # If there is more than one internal or external, return the first + # one. + if self._url is None: + if len(self.urls) > 1: + internals_urls = [u for u in self.urls \ + if u['is_external'] == False] + if len(internals_urls) >= 1: + self._url = internals_urls[0] + if self._url is None: + self._url = self.urls[0] + return self._url + + @property + def is_source(self): + """return if the distribution is a source one or not""" + return self.dist_type == 'sdist' + + def download(self, path=None): + """Download the distribution to a path, and return it. + + If the path is given in path, use this, otherwise, generates a new one + Return the download location. + """ + if path is None: + path = tempfile.mkdtemp() + + # if we do not have downloaded it yet, do it. + if self.downloaded_location is None: + url = self.url['url'] + archive_name = urllib.parse.urlparse(url)[2].split('/')[-1] + filename, headers = urllib.request.urlretrieve(url, + path + "/" + archive_name) + self.downloaded_location = filename + self._check_md5(filename) + return self.downloaded_location + + def unpack(self, path=None): + """Unpack the distribution to the given path. + + If not destination is given, creates a temporary location. + + Returns the location of the extracted files (root). + """ + if not self._unpacked_dir: + if path is None: + path = tempfile.mkdtemp() + + filename = self.download(path) + content_type = mimetypes.guess_type(filename)[0] + unpack_archive(filename, path) + self._unpacked_dir = path + + return path + + def _check_md5(self, filename): + """Check that the md5 checksum of the given file matches the one in + url param""" + hashname = self.url['hashname'] + expected_hashval = self.url['hashval'] + if not None in (expected_hashval, hashname): + with open(filename, 'rb') as f: + hashval = hashlib.new(hashname) + hashval.update(f.read()) + + if hashval.hexdigest() != expected_hashval: + raise HashDoesNotMatch("got %s instead of %s" + % (hashval.hexdigest(), expected_hashval)) + + def __repr__(self): + if self.release is None: + return "" % self.dist_type + + return "<%s %s %s>" % ( + self.release.name, self.release.version, self.dist_type or "") + + +class ReleasesList(IndexReference): + """A container of Release. + + Provides useful methods and facilities to sort and filter releases. + """ + def __init__(self, name, releases=None, contains_hidden=False, index=None): + self.set_index(index) + self.releases = [] + self.name = name + self.contains_hidden = contains_hidden + if releases: + self.add_releases(releases) + + def fetch_releases(self): + self._index.get_releases(self.name) + return self.releases + + def filter(self, predicate): + """Filter and return a subset of releases matching the given predicate. + """ + return ReleasesList(self.name, [release for release in self.releases + if predicate.match(release.version)], + index=self._index) + + def get_last(self, requirements, prefer_final=None): + """Return the "last" release, that satisfy the given predicates. + + "last" is defined by the version number of the releases, you also could + set prefer_final parameter to True or False to change the order results + """ + predicate = get_version_predicate(requirements) + releases = self.filter(predicate) + if len(releases) == 0: + return None + releases.sort_releases(prefer_final, reverse=True) + return releases[0] + + def add_releases(self, releases): + """Add releases in the release list. + + :param: releases is a list of ReleaseInfo objects. + """ + for r in releases: + self.add_release(release=r) + + def add_release(self, version=None, dist_type='sdist', release=None, + **dist_args): + """Add a release to the list. + + The release can be passed in the `release` parameter, and in this case, + it will be crawled to extract the useful informations if necessary, or + the release informations can be directly passed in the `version` and + `dist_type` arguments. + + Other keywords arguments can be provided, and will be forwarded to the + distribution creation (eg. the arguments of the DistInfo constructor). + """ + if release: + if release.name.lower() != self.name.lower(): + raise ValueError("%s is not the same project as %s" % + (release.name, self.name)) + version = str(release.version) + + if not version in self.get_versions(): + # append only if not already exists + self.releases.append(release) + for dist in release.dists.values(): + for url in dist.urls: + self.add_release(version, dist.dist_type, **url) + else: + matches = [r for r in self.releases + if str(r.version) == version and r.name == self.name] + if not matches: + release = ReleaseInfo(self.name, version, index=self._index) + self.releases.append(release) + else: + release = matches[0] + + release.add_distribution(dist_type=dist_type, **dist_args) + + def sort_releases(self, prefer_final=False, reverse=True, *args, **kwargs): + """Sort the results with the given properties. + + The `prefer_final` argument can be used to specify if final + distributions (eg. not dev, bet or alpha) would be prefered or not. + + Results can be inverted by using `reverse`. + + Any other parameter provided will be forwarded to the sorted call. You + cannot redefine the key argument of "sorted" here, as it is used + internally to sort the releases. + """ + + sort_by = [] + if prefer_final: + sort_by.append("is_final") + sort_by.append("version") + + self.releases.sort( + key=lambda i: tuple(getattr(i, arg) for arg in sort_by), + reverse=reverse, *args, **kwargs) + + def get_release(self, version): + """Return a release from its version.""" + matches = [r for r in self.releases if str(r.version) == version] + if len(matches) != 1: + raise KeyError(version) + return matches[0] + + def get_versions(self): + """Return a list of releases versions contained""" + return [str(r.version) for r in self.releases] + + def __getitem__(self, key): + return self.releases[key] + + def __len__(self): + return len(self.releases) + + def __repr__(self): + string = 'Project "%s"' % self.name + if self.get_versions(): + string += ' versions: %s' % ', '.join(self.get_versions()) + return '<%s>' % string + + +def get_infos_from_url(url, probable_dist_name=None, is_external=True): + """Get useful informations from an URL. + + Return a dict of (name, version, url, hashtype, hash, is_external) + + :param url: complete url of the distribution + :param probable_dist_name: A probable name of the project. + :param is_external: Tell if the url commes from an index or from + an external URL. + """ + # if the url contains a md5 hash, get it. + md5_hash = None + match = MD5_HASH.match(url) + if match is not None: + md5_hash = match.group(1) + # remove the hash + url = url.replace("#md5=%s" % md5_hash, "") + + # parse the archive name to find dist name and version + archive_name = urllib.parse.urlparse(url)[2].split('/')[-1] + extension_matched = False + # remove the extension from the name + for ext in EXTENSIONS: + if archive_name.endswith(ext): + archive_name = archive_name[:-len(ext)] + extension_matched = True + + name, version = split_archive_name(archive_name) + if extension_matched is True: + return {'name': name, + 'version': version, + 'url': url, + 'hashname': "md5", + 'hashval': md5_hash, + 'is_external': is_external, + 'dist_type': 'sdist'} + + +def split_archive_name(archive_name, probable_name=None): + """Split an archive name into two parts: name and version. + + Return the tuple (name, version) + """ + # Try to determine wich part is the name and wich is the version using the + # "-" separator. Take the larger part to be the version number then reduce + # if this not works. + def eager_split(str, maxsplit=2): + # split using the "-" separator + splits = str.rsplit("-", maxsplit) + name = splits[0] + version = "-".join(splits[1:]) + if version.startswith("-"): + version = version[1:] + if suggest_normalized_version(version) is None and maxsplit >= 0: + # we dont get a good version number: recurse ! + return eager_split(str, maxsplit - 1) + else: + return name, version + if probable_name is not None: + probable_name = probable_name.lower() + name = None + if probable_name is not None and probable_name in archive_name: + # we get the name from probable_name, if given. + name = probable_name + version = archive_name.lstrip(name) + else: + name, version = eager_split(archive_name) + + version = suggest_normalized_version(version) + if version is not None and name != "": + return name.lower(), version + else: + raise CantParseArchiveName(archive_name) diff --git a/Lib/packaging/pypi/errors.py b/Lib/packaging/pypi/errors.py new file mode 100644 index 0000000000..2191ac100c --- /dev/null +++ b/Lib/packaging/pypi/errors.py @@ -0,0 +1,39 @@ +"""Exceptions raised by packaging.pypi code.""" + +from packaging.errors import PackagingPyPIError + + +class ProjectNotFound(PackagingPyPIError): + """Project has not been found""" + + +class DistributionNotFound(PackagingPyPIError): + """The release has not been found""" + + +class ReleaseNotFound(PackagingPyPIError): + """The release has not been found""" + + +class CantParseArchiveName(PackagingPyPIError): + """An archive name can't be parsed to find distribution name and version""" + + +class DownloadError(PackagingPyPIError): + """An error has occurs while downloading""" + + +class HashDoesNotMatch(DownloadError): + """Compared hashes does not match""" + + +class UnsupportedHashName(PackagingPyPIError): + """A unsupported hashname has been used""" + + +class UnableToDownload(PackagingPyPIError): + """All mirrors have been tried, without success""" + + +class InvalidSearchField(PackagingPyPIError): + """An invalid search field has been used""" diff --git a/Lib/packaging/pypi/mirrors.py b/Lib/packaging/pypi/mirrors.py new file mode 100644 index 0000000000..a646acff3c --- /dev/null +++ b/Lib/packaging/pypi/mirrors.py @@ -0,0 +1,52 @@ +"""Utilities related to the mirror infrastructure defined in PEP 381.""" + +from string import ascii_lowercase +import socket + +DEFAULT_MIRROR_URL = "last.pypi.python.org" + + +def get_mirrors(hostname=None): + """Return the list of mirrors from the last record found on the DNS + entry:: + + >>> from packaging.pypi.mirrors import get_mirrors + >>> get_mirrors() + ['a.pypi.python.org', 'b.pypi.python.org', 'c.pypi.python.org', + 'd.pypi.python.org'] + + """ + if hostname is None: + hostname = DEFAULT_MIRROR_URL + + # return the last mirror registered on PyPI. + try: + hostname = socket.gethostbyname_ex(hostname)[0] + except socket.gaierror: + return [] + end_letter = hostname.split(".", 1) + + # determine the list from the last one. + return ["%s.%s" % (s, end_letter[1]) for s in string_range(end_letter[0])] + + +def string_range(last): + """Compute the range of string between "a" and last. + + This works for simple "a to z" lists, but also for "a to zz" lists. + """ + for k in range(len(last)): + for x in product(ascii_lowercase, repeat=(k + 1)): + result = ''.join(x) + yield result + if result == last: + return + + +def product(*args, **kwds): + pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1) + result = [[]] + for pool in pools: + result = [x + [y] for x in result for y in pool] + for prod in result: + yield tuple(prod) diff --git a/Lib/packaging/pypi/simple.py b/Lib/packaging/pypi/simple.py new file mode 100644 index 0000000000..8585193883 --- /dev/null +++ b/Lib/packaging/pypi/simple.py @@ -0,0 +1,452 @@ +"""Spider using the screen-scraping "simple" PyPI API. + +This module contains the class SimpleIndexCrawler, a simple spider that +can be used to find and retrieve distributions from a project index +(like the Python Package Index), using its so-called simple API (see +reference implementation available at http://pypi.python.org/simple/). +""" + +import http.client +import re +import socket +import sys +import urllib.request +import urllib.parse +import urllib.error +import os + + +from fnmatch import translate +from packaging import logger +from packaging.metadata import Metadata +from packaging.version import get_version_predicate +from packaging import __version__ as packaging_version +from packaging.pypi.base import BaseClient +from packaging.pypi.dist import (ReleasesList, EXTENSIONS, + get_infos_from_url, MD5_HASH) +from packaging.pypi.errors import (PackagingPyPIError, DownloadError, + UnableToDownload, CantParseArchiveName, + ReleaseNotFound, ProjectNotFound) +from packaging.pypi.mirrors import get_mirrors +from packaging.metadata import Metadata + +__all__ = ['Crawler', 'DEFAULT_SIMPLE_INDEX_URL'] + +# -- Constants ----------------------------------------------- +DEFAULT_SIMPLE_INDEX_URL = "http://a.pypi.python.org/simple/" +DEFAULT_HOSTS = ("*",) +SOCKET_TIMEOUT = 15 +USER_AGENT = "Python-urllib/%s packaging/%s" % ( + sys.version[:3], packaging_version) + +# -- Regexps ------------------------------------------------- +EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$') +HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I) +URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match + +# This pattern matches a character entity reference (a decimal numeric +# references, a hexadecimal numeric reference, or a named reference). +ENTITY_SUB = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub +REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) + + +def socket_timeout(timeout=SOCKET_TIMEOUT): + """Decorator to add a socket timeout when requesting pages on PyPI. + """ + def _socket_timeout(func): + def _socket_timeout(self, *args, **kwargs): + old_timeout = socket.getdefaulttimeout() + if hasattr(self, "_timeout"): + timeout = self._timeout + socket.setdefaulttimeout(timeout) + try: + return func(self, *args, **kwargs) + finally: + socket.setdefaulttimeout(old_timeout) + return _socket_timeout + return _socket_timeout + + +def with_mirror_support(): + """Decorator that makes the mirroring support easier""" + def wrapper(func): + def wrapped(self, *args, **kwargs): + try: + return func(self, *args, **kwargs) + except DownloadError: + # if an error occurs, try with the next index_url + if self._mirrors_tries >= self._mirrors_max_tries: + try: + self._switch_to_next_mirror() + except KeyError: + raise UnableToDownload("Tried all mirrors") + else: + self._mirrors_tries += 1 + self._projects.clear() + return wrapped(self, *args, **kwargs) + return wrapped + return wrapper + + +class Crawler(BaseClient): + """Provides useful tools to request the Python Package Index simple API. + + You can specify both mirrors and mirrors_url, but mirrors_url will only be + used if mirrors is set to None. + + :param index_url: the url of the simple index to search on. + :param prefer_final: if the version is not mentioned, and the last + version is not a "final" one (alpha, beta, etc.), + pick up the last final version. + :param prefer_source: if the distribution type is not mentioned, pick up + the source one if available. + :param follow_externals: tell if following external links is needed or + not. Default is False. + :param hosts: a list of hosts allowed to be processed while using + follow_externals=True. Default behavior is to follow all + hosts. + :param follow_externals: tell if following external links is needed or + not. Default is False. + :param mirrors_url: the url to look on for DNS records giving mirror + adresses. + :param mirrors: a list of mirrors (see PEP 381). + :param timeout: time in seconds to consider a url has timeouted. + :param mirrors_max_tries": number of times to try requesting informations + on mirrors before switching. + """ + + def __init__(self, index_url=DEFAULT_SIMPLE_INDEX_URL, prefer_final=False, + prefer_source=True, hosts=DEFAULT_HOSTS, + follow_externals=False, mirrors_url=None, mirrors=None, + timeout=SOCKET_TIMEOUT, mirrors_max_tries=0): + super(Crawler, self).__init__(prefer_final, prefer_source) + self.follow_externals = follow_externals + + # mirroring attributes. + if not index_url.endswith("/"): + index_url += "/" + # if no mirrors are defined, use the method described in PEP 381. + if mirrors is None: + mirrors = get_mirrors(mirrors_url) + self._mirrors = set(mirrors) + self._mirrors_used = set() + self.index_url = index_url + self._mirrors_max_tries = mirrors_max_tries + self._mirrors_tries = 0 + self._timeout = timeout + + # create a regexp to match all given hosts + self._allowed_hosts = re.compile('|'.join(map(translate, hosts))).match + + # we keep an index of pages we have processed, in order to avoid + # scanning them multple time (eg. if there is multiple pages pointing + # on one) + self._processed_urls = [] + self._projects = {} + + @with_mirror_support() + def search_projects(self, name=None, **kwargs): + """Search the index for projects containing the given name. + + Return a list of names. + """ + with self._open_url(self.index_url) as index: + if '*' in name: + name.replace('*', '.*') + else: + name = "%s%s%s" % ('*.?', name, '*.?') + name = name.replace('*', '[^<]*') # avoid matching end tag + projectname = re.compile(']*>(%s)' % name, re.I) + matching_projects = [] + + index_content = index.read() + + # FIXME should use bytes I/O and regexes instead of decoding + index_content = index_content.decode() + + for match in projectname.finditer(index_content): + project_name = match.group(1) + matching_projects.append(self._get_project(project_name)) + return matching_projects + + def get_releases(self, requirements, prefer_final=None, + force_update=False): + """Search for releases and return a ReleaseList object containing + the results. + """ + predicate = get_version_predicate(requirements) + if predicate.name.lower() in self._projects and not force_update: + return self._projects.get(predicate.name.lower()) + prefer_final = self._get_prefer_final(prefer_final) + logger.info('reading info on PyPI about %s', predicate.name) + self._process_index_page(predicate.name) + + if predicate.name.lower() not in self._projects: + raise ProjectNotFound() + + releases = self._projects.get(predicate.name.lower()) + releases.sort_releases(prefer_final=prefer_final) + return releases + + def get_release(self, requirements, prefer_final=None): + """Return only one release that fulfill the given requirements""" + predicate = get_version_predicate(requirements) + release = self.get_releases(predicate, prefer_final)\ + .get_last(predicate) + if not release: + raise ReleaseNotFound("No release matches the given criterias") + return release + + def get_distributions(self, project_name, version): + """Return the distributions found on the index for the specific given + release""" + # as the default behavior of get_release is to return a release + # containing the distributions, just alias it. + return self.get_release("%s (%s)" % (project_name, version)) + + def get_metadata(self, project_name, version): + """Return the metadatas from the simple index. + + Currently, download one archive, extract it and use the PKG-INFO file. + """ + release = self.get_distributions(project_name, version) + if not release.metadata: + location = release.get_distribution().unpack() + pkg_info = os.path.join(location, 'PKG-INFO') + release.metadata = Metadata(pkg_info) + return release + + def _switch_to_next_mirror(self): + """Switch to the next mirror (eg. point self.index_url to the next + mirror url. + + Raise a KeyError if all mirrors have been tried. + """ + self._mirrors_used.add(self.index_url) + index_url = self._mirrors.pop() + if not ("http://" or "https://" or "file://") in index_url: + index_url = "http://%s" % index_url + + if not index_url.endswith("/simple"): + index_url = "%s/simple/" % index_url + + self.index_url = index_url + + def _is_browsable(self, url): + """Tell if the given URL can be browsed or not. + + It uses the follow_externals and the hosts list to tell if the given + url is browsable or not. + """ + # if _index_url is contained in the given URL, we are browsing the + # index, and it's always "browsable". + # local files are always considered browable resources + if self.index_url in url or urllib.parse.urlparse(url)[0] == "file": + return True + elif self.follow_externals: + if self._allowed_hosts(urllib.parse.urlparse(url)[1]): # 1 is netloc + return True + else: + return False + return False + + def _is_distribution(self, link): + """Tell if the given URL matches to a distribution name or not. + """ + #XXX find a better way to check that links are distributions + # Using a regexp ? + for ext in EXTENSIONS: + if ext in link: + return True + return False + + def _register_release(self, release=None, release_info={}): + """Register a new release. + + Both a release or a dict of release_info can be provided, the prefered + way (eg. the quicker) is the dict one. + + Return the list of existing releases for the given project. + """ + # Check if the project already has a list of releases (refering to + # the project name). If not, create a new release list. + # Then, add the release to the list. + if release: + name = release.name + else: + name = release_info['name'] + if not name.lower() in self._projects: + self._projects[name.lower()] = ReleasesList(name, index=self._index) + + if release: + self._projects[name.lower()].add_release(release=release) + else: + name = release_info.pop('name') + version = release_info.pop('version') + dist_type = release_info.pop('dist_type') + self._projects[name.lower()].add_release(version, dist_type, + **release_info) + return self._projects[name.lower()] + + def _process_url(self, url, project_name=None, follow_links=True): + """Process an url and search for distributions packages. + + For each URL found, if it's a download, creates a PyPIdistribution + object. If it's a homepage and we can follow links, process it too. + + :param url: the url to process + :param project_name: the project name we are searching for. + :param follow_links: Do not want to follow links more than from one + level. This parameter tells if we want to follow + the links we find (eg. run recursively this + method on it) + """ + with self._open_url(url) as f: + base_url = f.url + if url not in self._processed_urls: + self._processed_urls.append(url) + link_matcher = self._get_link_matcher(url) + for link, is_download in link_matcher(f.read().decode(), base_url): + if link not in self._processed_urls: + if self._is_distribution(link) or is_download: + self._processed_urls.append(link) + # it's a distribution, so create a dist object + try: + infos = get_infos_from_url(link, project_name, + is_external=not self.index_url in url) + except CantParseArchiveName as e: + logger.warning( + "version has not been parsed: %s", e) + else: + self._register_release(release_info=infos) + else: + if self._is_browsable(link) and follow_links: + self._process_url(link, project_name, + follow_links=False) + + def _get_link_matcher(self, url): + """Returns the right link matcher function of the given url + """ + if self.index_url in url: + return self._simple_link_matcher + else: + return self._default_link_matcher + + def _get_full_url(self, url, base_url): + return urllib.parse.urljoin(base_url, self._htmldecode(url)) + + def _simple_link_matcher(self, content, base_url): + """Yield all links with a rel="download" or rel="homepage". + + This matches the simple index requirements for matching links. + If follow_externals is set to False, dont yeld the external + urls. + + :param content: the content of the page we want to parse + :param base_url: the url of this page. + """ + for match in HREF.finditer(content): + url = self._get_full_url(match.group(1), base_url) + if MD5_HASH.match(url): + yield (url, True) + + for match in REL.finditer(content): + # search for rel links. + tag, rel = match.groups() + rels = [s.strip() for s in rel.lower().split(',')] + if 'homepage' in rels or 'download' in rels: + for match in HREF.finditer(tag): + url = self._get_full_url(match.group(1), base_url) + if 'download' in rels or self._is_browsable(url): + # yield a list of (url, is_download) + yield (url, 'download' in rels) + + def _default_link_matcher(self, content, base_url): + """Yield all links found on the page. + """ + for match in HREF.finditer(content): + url = self._get_full_url(match.group(1), base_url) + if self._is_browsable(url): + yield (url, False) + + @with_mirror_support() + def _process_index_page(self, name): + """Find and process a PyPI page for the given project name. + + :param name: the name of the project to find the page + """ + # Browse and index the content of the given PyPI page. + url = self.index_url + name + "/" + self._process_url(url, name) + + @socket_timeout() + def _open_url(self, url): + """Open a urllib2 request, handling HTTP authentication, and local + files support. + + """ + scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) + + # authentication stuff + if scheme in ('http', 'https'): + auth, host = urllib.parse.splituser(netloc) + else: + auth = None + + # add index.html automatically for filesystem paths + if scheme == 'file': + if url.endswith('/'): + url += "index.html" + + # add authorization headers if auth is provided + if auth: + auth = "Basic " + \ + urllib.parse.unquote(auth).encode('base64').strip() + new_url = urllib.parse.urlunparse(( + scheme, host, path, params, query, frag)) + request = urllib.request.Request(new_url) + request.add_header("Authorization", auth) + else: + request = urllib.request.Request(url) + request.add_header('User-Agent', USER_AGENT) + try: + fp = urllib.request.urlopen(request) + except (ValueError, http.client.InvalidURL) as v: + msg = ' '.join([str(arg) for arg in v.args]) + raise PackagingPyPIError('%s %s' % (url, msg)) + except urllib.error.HTTPError as v: + return v + except urllib.error.URLError as v: + raise DownloadError("Download error for %s: %s" % (url, v.reason)) + except http.client.BadStatusLine as v: + raise DownloadError('%s returned a bad status line. ' + 'The server might be down, %s' % (url, v.line)) + except http.client.HTTPException as v: + raise DownloadError("Download error for %s: %s" % (url, v)) + except socket.timeout: + raise DownloadError("The server timeouted") + + if auth: + # Put authentication info back into request URL if same host, + # so that links found on the page will work + s2, h2, path2, param2, query2, frag2 = \ + urllib.parse.urlparse(fp.url) + if s2 == scheme and h2 == host: + fp.url = urllib.parse.urlunparse( + (s2, netloc, path2, param2, query2, frag2)) + return fp + + def _decode_entity(self, match): + what = match.group(1) + if what.startswith('#x'): + what = int(what[2:], 16) + elif what.startswith('#'): + what = int(what[1:]) + else: + from html.entities import name2codepoint + what = name2codepoint.get(what, match.group(0)) + return chr(what) + + def _htmldecode(self, text): + """Decode HTML entities in the given text.""" + return ENTITY_SUB(self._decode_entity, text) diff --git a/Lib/packaging/pypi/wrapper.py b/Lib/packaging/pypi/wrapper.py new file mode 100644 index 0000000000..945d08abb7 --- /dev/null +++ b/Lib/packaging/pypi/wrapper.py @@ -0,0 +1,99 @@ +"""Convenient client for all PyPI APIs. + +This module provides a ClientWrapper class which will use the "simple" +or XML-RPC API to request information or files from an index. +""" + +from packaging.pypi import simple, xmlrpc + +_WRAPPER_MAPPINGS = {'get_release': 'simple', + 'get_releases': 'simple', + 'search_projects': 'simple', + 'get_metadata': 'xmlrpc', + 'get_distributions': 'simple'} + +_WRAPPER_INDEXES = {'xmlrpc': xmlrpc.Client, + 'simple': simple.Crawler} + + +def switch_index_if_fails(func, wrapper): + """Decorator that switch of index (for instance from xmlrpc to simple) + if the first mirror return an empty list or raises an exception. + """ + def decorator(*args, **kwargs): + retry = True + exception = None + methods = [func] + for f in wrapper._indexes.values(): + if f != func.__self__ and hasattr(f, func.__name__): + methods.append(getattr(f, func.__name__)) + for method in methods: + try: + response = method(*args, **kwargs) + retry = False + except Exception as e: + exception = e + if not retry: + break + if retry and exception: + raise exception + else: + return response + return decorator + + +class ClientWrapper: + """Wrapper around simple and xmlrpc clients, + + Choose the best implementation to use depending the needs, using the given + mappings. + If one of the indexes returns an error, tries to use others indexes. + + :param index: tell which index to rely on by default. + :param index_classes: a dict of name:class to use as indexes. + :param indexes: a dict of name:index already instantiated + :param mappings: the mappings to use for this wrapper + """ + + def __init__(self, default_index='simple', index_classes=_WRAPPER_INDEXES, + indexes={}, mappings=_WRAPPER_MAPPINGS): + self._projects = {} + self._mappings = mappings + self._indexes = indexes + self._default_index = default_index + + # instantiate the classes and set their _project attribute to the one + # of the wrapper. + for name, cls in index_classes.items(): + obj = self._indexes.setdefault(name, cls()) + obj._projects = self._projects + obj._index = self + + def __getattr__(self, method_name): + """When asking for methods of the wrapper, return the implementation of + the wrapped classes, depending the mapping. + + Decorate the methods to switch of implementation if an error occurs + """ + real_method = None + if method_name in _WRAPPER_MAPPINGS: + obj = self._indexes[_WRAPPER_MAPPINGS[method_name]] + real_method = getattr(obj, method_name) + else: + # the method is not defined in the mappings, so we try first to get + # it via the default index, and rely on others if needed. + try: + real_method = getattr(self._indexes[self._default_index], + method_name) + except AttributeError: + other_indexes = [i for i in self._indexes + if i != self._default_index] + for index in other_indexes: + real_method = getattr(self._indexes[index], method_name, + None) + if real_method: + break + if real_method: + return switch_index_if_fails(real_method, self) + else: + raise AttributeError("No index have attribute '%s'" % method_name) diff --git a/Lib/packaging/pypi/xmlrpc.py b/Lib/packaging/pypi/xmlrpc.py new file mode 100644 index 0000000000..7a9f6cc713 --- /dev/null +++ b/Lib/packaging/pypi/xmlrpc.py @@ -0,0 +1,200 @@ +"""Spider using the XML-RPC PyPI API. + +This module contains the class Client, a spider that can be used to find +and retrieve distributions from a project index (like the Python Package +Index), using its XML-RPC API (see documentation of the reference +implementation at http://wiki.python.org/moin/PyPiXmlRpc). +""" + +import xmlrpc.client + +from packaging import logger +from packaging.errors import IrrationalVersionError +from packaging.version import get_version_predicate +from packaging.pypi.base import BaseClient +from packaging.pypi.errors import (ProjectNotFound, InvalidSearchField, + ReleaseNotFound) +from packaging.pypi.dist import ReleaseInfo + +__all__ = ['Client', 'DEFAULT_XMLRPC_INDEX_URL'] + +DEFAULT_XMLRPC_INDEX_URL = 'http://python.org/pypi' + +_SEARCH_FIELDS = ['name', 'version', 'author', 'author_email', 'maintainer', + 'maintainer_email', 'home_page', 'license', 'summary', + 'description', 'keywords', 'platform', 'download_url'] + + +class Client(BaseClient): + """Client to query indexes using XML-RPC method calls. + + If no server_url is specified, use the default PyPI XML-RPC URL, + defined in the DEFAULT_XMLRPC_INDEX_URL constant:: + + >>> client = XMLRPCClient() + >>> client.server_url == DEFAULT_XMLRPC_INDEX_URL + True + + >>> client = XMLRPCClient("http://someurl/") + >>> client.server_url + 'http://someurl/' + """ + + def __init__(self, server_url=DEFAULT_XMLRPC_INDEX_URL, prefer_final=False, + prefer_source=True): + super(Client, self).__init__(prefer_final, prefer_source) + self.server_url = server_url + self._projects = {} + + def get_release(self, requirements, prefer_final=False): + """Return a release with all complete metadata and distribution + related informations. + """ + prefer_final = self._get_prefer_final(prefer_final) + predicate = get_version_predicate(requirements) + releases = self.get_releases(predicate.name) + release = releases.get_last(predicate, prefer_final) + self.get_metadata(release.name, str(release.version)) + self.get_distributions(release.name, str(release.version)) + return release + + def get_releases(self, requirements, prefer_final=None, show_hidden=True, + force_update=False): + """Return the list of existing releases for a specific project. + + Cache the results from one call to another. + + If show_hidden is True, return the hidden releases too. + If force_update is True, reprocess the index to update the + informations (eg. make a new XML-RPC call). + :: + + >>> client = XMLRPCClient() + >>> client.get_releases('Foo') + ['1.1', '1.2', '1.3'] + + If no such project exists, raise a ProjectNotFound exception:: + + >>> client.get_project_versions('UnexistingProject') + ProjectNotFound: UnexistingProject + + """ + def get_versions(project_name, show_hidden): + return self.proxy.package_releases(project_name, show_hidden) + + predicate = get_version_predicate(requirements) + prefer_final = self._get_prefer_final(prefer_final) + project_name = predicate.name + if not force_update and (project_name.lower() in self._projects): + project = self._projects[project_name.lower()] + if not project.contains_hidden and show_hidden: + # if hidden releases are requested, and have an existing + # list of releases that does not contains hidden ones + all_versions = get_versions(project_name, show_hidden) + existing_versions = project.get_versions() + hidden_versions = set(all_versions) - set(existing_versions) + for version in hidden_versions: + project.add_release(release=ReleaseInfo(project_name, + version, index=self._index)) + else: + versions = get_versions(project_name, show_hidden) + if not versions: + raise ProjectNotFound(project_name) + project = self._get_project(project_name) + project.add_releases([ReleaseInfo(project_name, version, + index=self._index) + for version in versions]) + project = project.filter(predicate) + if len(project) == 0: + raise ReleaseNotFound("%s" % predicate) + project.sort_releases(prefer_final) + return project + + + def get_distributions(self, project_name, version): + """Grab informations about distributions from XML-RPC. + + Return a ReleaseInfo object, with distribution-related informations + filled in. + """ + url_infos = self.proxy.release_urls(project_name, version) + project = self._get_project(project_name) + if version not in project.get_versions(): + project.add_release(release=ReleaseInfo(project_name, version, + index=self._index)) + release = project.get_release(version) + for info in url_infos: + packagetype = info['packagetype'] + dist_infos = {'url': info['url'], + 'hashval': info['md5_digest'], + 'hashname': 'md5', + 'is_external': False, + 'python_version': info['python_version']} + release.add_distribution(packagetype, **dist_infos) + return release + + def get_metadata(self, project_name, version): + """Retrieve project metadata. + + Return a ReleaseInfo object, with metadata informations filled in. + """ + # to be case-insensitive, get the informations from the XMLRPC API + projects = [d['name'] for d in + self.proxy.search({'name': project_name}) + if d['name'].lower() == project_name] + if len(projects) > 0: + project_name = projects[0] + + metadata = self.proxy.release_data(project_name, version) + project = self._get_project(project_name) + if version not in project.get_versions(): + project.add_release(release=ReleaseInfo(project_name, version, + index=self._index)) + release = project.get_release(version) + release.set_metadata(metadata) + return release + + def search_projects(self, name=None, operator="or", **kwargs): + """Find using the keys provided in kwargs. + + You can set operator to "and" or "or". + """ + for key in kwargs: + if key not in _SEARCH_FIELDS: + raise InvalidSearchField(key) + if name: + kwargs["name"] = name + projects = self.proxy.search(kwargs, operator) + for p in projects: + project = self._get_project(p['name']) + try: + project.add_release(release=ReleaseInfo(p['name'], + p['version'], metadata={'summary': p['summary']}, + index=self._index)) + except IrrationalVersionError as e: + logger.warning("Irrational version error found: %s", e) + return [self._projects[p['name'].lower()] for p in projects] + + def get_all_projects(self): + """Return the list of all projects registered in the package index""" + projects = self.proxy.list_packages() + for name in projects: + self.get_releases(name, show_hidden=True) + + return [self._projects[name.lower()] for name in set(projects)] + + @property + def proxy(self): + """Property used to return the XMLRPC server proxy. + + If no server proxy is defined yet, creates a new one:: + + >>> client = XmlRpcClient() + >>> client.proxy() + + + """ + if not hasattr(self, '_server_proxy'): + self._server_proxy = xmlrpc.client.ServerProxy(self.server_url) + + return self._server_proxy diff --git a/Lib/packaging/resources.py b/Lib/packaging/resources.py new file mode 100644 index 0000000000..e5904f360d --- /dev/null +++ b/Lib/packaging/resources.py @@ -0,0 +1,25 @@ +"""Data file path abstraction. + +Functions in this module use sysconfig to find the paths to the resource +files registered in project's setup.cfg file. See the documentation for +more information. +""" +# TODO write that documentation + +from packaging.database import get_distribution + +__all__ = ['get_file_path', 'get_file'] + + +def get_file_path(distribution_name, relative_path): + """Return the path to a resource file.""" + dist = get_distribution(distribution_name) + if dist != None: + return dist.get_resource_path(relative_path) + raise LookupError('no distribution named %r found' % distribution_name) + + +def get_file(distribution_name, relative_path, *args, **kwargs): + """Open and return a resource file.""" + return open(get_file_path(distribution_name, relative_path), + *args, **kwargs) diff --git a/Lib/packaging/run.py b/Lib/packaging/run.py new file mode 100644 index 0000000000..1d4fadb880 --- /dev/null +++ b/Lib/packaging/run.py @@ -0,0 +1,645 @@ +"""Main command line parser. Implements the pysetup script.""" + +import os +import re +import sys +import getopt +import logging + +from packaging import logger +from packaging.dist import Distribution +from packaging.util import _is_archive_file +from packaging.command import get_command_class, STANDARD_COMMANDS +from packaging.install import install, install_local_project, remove +from packaging.database import get_distribution, get_distributions +from packaging.depgraph import generate_graph +from packaging.fancy_getopt import FancyGetopt +from packaging.errors import (PackagingArgError, PackagingError, + PackagingModuleError, PackagingClassError, + CCompilerError) + + +command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') + +common_usage = """\ +Actions: +%(actions)s + +To get more help on an action, use: + + pysetup action --help +""" + +create_usage = """\ +Usage: pysetup create + or: pysetup create --help + +Create a new Python package. +""" + +graph_usage = """\ +Usage: pysetup graph dist + or: pysetup graph --help + +Print dependency graph for the distribution. + +positional arguments: + dist installed distribution name +""" + +install_usage = """\ +Usage: pysetup install [dist] + or: pysetup install [archive] + or: pysetup install [src_dir] + or: pysetup install --help + +Install a Python distribution from the indexes, source directory, or sdist. + +positional arguments: + archive path to source distribution (zip, tar.gz) + dist distribution name to install from the indexes + scr_dir path to source directory + +""" + +metadata_usage = """\ +Usage: pysetup metadata [dist] [-f field ...] + or: pysetup metadata [dist] [--all] + or: pysetup metadata --help + +Print metadata for the distribution. + +positional arguments: + dist installed distribution name + +optional arguments: + -f metadata field to print + --all print all metadata fields +""" + +remove_usage = """\ +Usage: pysetup remove dist [-y] + or: pysetup remove --help + +Uninstall a Python distribution. + +positional arguments: + dist installed distribution name + +optional arguments: + -y auto confirm package removal +""" + +run_usage = """\ +Usage: pysetup run [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] + or: pysetup run --help + or: pysetup run --list-commands + or: pysetup run cmd --help +""" + +list_usage = """\ +Usage: pysetup list dist [dist ...] + or: pysetup list --help + or: pysetup list --all + +Print name, version and location for the matching installed distributions. + +positional arguments: + dist installed distribution name + +optional arguments: + --all list all installed distributions +""" + +search_usage = """\ +Usage: pysetup search [project] [--simple [url]] [--xmlrpc [url] [--fieldname value ...] --operator or|and] + or: pysetup search --help + +Search the indexes for the matching projects. + +positional arguments: + project the project pattern to search for + +optional arguments: + --xmlrpc [url] wether to use the xmlrpc index or not. If an url is + specified, it will be used rather than the default one. + + --simple [url] wether to use the simple index or not. If an url is + specified, it will be used rather than the default one. + + --fieldname value Make a search on this field. Can only be used if + --xmlrpc has been selected or is the default index. + + --operator or|and Defines what is the operator to use when doing xmlrpc + searchs with multiple fieldnames. Can only be used if + --xmlrpc has been selected or is the default index. +""" + +global_options = [ + # The fourth entry for verbose means that it can be repeated. + ('verbose', 'v', "run verbosely (default)", True), + ('quiet', 'q', "run quietly (turns verbosity off)"), + ('dry-run', 'n', "don't actually do anything"), + ('help', 'h', "show detailed help message"), + ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'), + ('version', None, 'Display the version'), +] + +negative_opt = {'quiet': 'verbose'} + +display_options = [ + ('help-commands', None, "list all available commands"), +] + +display_option_names = [x[0].replace('-', '_') for x in display_options] + + +def _parse_args(args, options, long_options): + """Transform sys.argv input into a dict. + + :param args: the args to parse (i.e sys.argv) + :param options: the list of options to pass to getopt + :param long_options: the list of string with the names of the long options + to be passed to getopt. + + The function returns a dict with options/long_options as keys and matching + values as values. + """ + optlist, args = getopt.gnu_getopt(args, options, long_options) + optdict = {} + optdict['args'] = args + for k, v in optlist: + k = k.lstrip('-') + if k not in optdict: + optdict[k] = [] + if v: + optdict[k].append(v) + else: + optdict[k].append(v) + return optdict + + +class action_help: + """Prints a help message when the standard help flags: -h and --help + are used on the commandline. + """ + + def __init__(self, help_msg): + self.help_msg = help_msg + + def __call__(self, f): + def wrapper(*args, **kwargs): + f_args = args[1] + if '--help' in f_args or '-h' in f_args: + print(self.help_msg) + return + return f(*args, **kwargs) + return wrapper + + +@action_help(create_usage) +def _create(distpatcher, args, **kw): + from packaging.create import main + return main() + + +@action_help(graph_usage) +def _graph(dispatcher, args, **kw): + name = args[1] + dist = get_distribution(name, use_egg_info=True) + if dist is None: + print('Distribution not found.') + else: + dists = get_distributions(use_egg_info=True) + graph = generate_graph(dists) + print(graph.repr_node(dist)) + + +@action_help(install_usage) +def _install(dispatcher, args, **kw): + # first check if we are in a source directory + if len(args) < 2: + # are we inside a project dir? + listing = os.listdir(os.getcwd()) + if 'setup.py' in listing or 'setup.cfg' in listing: + args.insert(1, os.getcwd()) + else: + logger.warning('no project to install') + return + + # installing from a source dir or archive file? + if os.path.isdir(args[1]) or _is_archive_file(args[1]): + install_local_project(args[1]) + else: + # download from PyPI + install(args[1]) + + +@action_help(metadata_usage) +def _metadata(dispatcher, args, **kw): + opts = _parse_args(args[1:], 'f:', ['all']) + if opts['args']: + name = opts['args'][0] + dist = get_distribution(name, use_egg_info=True) + if dist is None: + logger.warning('%s not installed', name) + return + else: + logger.info('searching local dir for metadata') + dist = Distribution() + dist.parse_config_files() + + metadata = dist.metadata + + if 'all' in opts: + keys = metadata.keys() + else: + if 'f' in opts: + keys = (k for k in opts['f'] if k in metadata) + else: + keys = () + + for key in keys: + if key in metadata: + print(metadata._convert_name(key) + ':') + value = metadata[key] + if isinstance(value, list): + for v in value: + print(' ' + v) + else: + print(' ' + value.replace('\n', '\n ')) + + +@action_help(remove_usage) +def _remove(distpatcher, args, **kw): + opts = _parse_args(args[1:], 'y', []) + if 'y' in opts: + auto_confirm = True + else: + auto_confirm = False + + for dist in set(opts['args']): + try: + remove(dist, auto_confirm=auto_confirm) + except PackagingError: + logger.warning('%s not installed', dist) + + +@action_help(run_usage) +def _run(dispatcher, args, **kw): + parser = dispatcher.parser + args = args[1:] + + commands = STANDARD_COMMANDS # + extra commands + + if args == ['--list-commands']: + print('List of available commands:') + cmds = sorted(commands) + + for cmd in cmds: + cls = dispatcher.cmdclass.get(cmd) or get_command_class(cmd) + desc = getattr(cls, 'description', + '(no description available)') + print(' %s: %s' % (cmd, desc)) + return + + while args: + args = dispatcher._parse_command_opts(parser, args) + if args is None: + return + + # create the Distribution class + # need to feed setup.cfg here ! + dist = Distribution() + + # Find and parse the config file(s): they will override options from + # the setup script, but be overridden by the command line. + + # XXX still need to be extracted from Distribution + dist.parse_config_files() + + try: + for cmd in dispatcher.commands: + dist.run_command(cmd, dispatcher.command_options[cmd]) + + except KeyboardInterrupt: + raise SystemExit("interrupted") + except (IOError, os.error, PackagingError, CCompilerError) as msg: + raise SystemExit("error: " + str(msg)) + + # XXX this is crappy + return dist + + +@action_help(list_usage) +def _list(dispatcher, args, **kw): + opts = _parse_args(args[1:], '', ['all']) + dists = get_distributions(use_egg_info=True) + if 'all' in opts: + results = dists + else: + results = [d for d in dists if d.name.lower() in opts['args']] + + for dist in results: + print('%s %s at %s' % (dist.name, dist.metadata['version'], dist.path)) + + +@action_help(search_usage) +def _search(dispatcher, args, **kw): + """The search action. + + It is able to search for a specific index (specified with --index), using + the simple or xmlrpc index types (with --type xmlrpc / --type simple) + """ + opts = _parse_args(args[1:], '', ['simple', 'xmlrpc']) + # 1. what kind of index is requested ? (xmlrpc / simple) + + +actions = [ + ('run', 'Run one or several commands', _run), + ('metadata', 'Display the metadata of a project', _metadata), + ('install', 'Install a project', _install), + ('remove', 'Remove a project', _remove), + ('search', 'Search for a project in the indexes', _search), + ('list', 'Search for local projects', _list), + ('graph', 'Display a graph', _graph), + ('create', 'Create a Project', _create), +] + + +class Dispatcher: + """Reads the command-line options + """ + def __init__(self, args=None): + self.verbose = 1 + self.dry_run = False + self.help = False + self.script_name = 'pysetup' + self.cmdclass = {} + self.commands = [] + self.command_options = {} + + for attr in display_option_names: + setattr(self, attr, False) + + self.parser = FancyGetopt(global_options + display_options) + self.parser.set_negative_aliases(negative_opt) + # FIXME this parses everything, including command options (e.g. "run + # build -i" errors with "option -i not recognized") + args = self.parser.getopt(args=args, object=self) + + # if first arg is "run", we have some commands + if len(args) == 0: + self.action = None + else: + self.action = args[0] + + allowed = [action[0] for action in actions] + [None] + if self.action not in allowed: + msg = 'Unrecognized action "%s"' % self.action + raise PackagingArgError(msg) + + # setting up the logging level from the command-line options + # -q gets warning, error and critical + if self.verbose == 0: + level = logging.WARNING + # default level or -v gets info too + # XXX there's a bug somewhere: the help text says that -v is default + # (and verbose is set to 1 above), but when the user explicitly gives + # -v on the command line, self.verbose is incremented to 2! Here we + # compensate for that (I tested manually). On a related note, I think + # it's a good thing to use -q/nothing/-v/-vv on the command line + # instead of logging constants; it will be easy to add support for + # logging configuration in setup.cfg for advanced users. --merwok + elif self.verbose in (1, 2): + level = logging.INFO + else: # -vv and more for debug + level = logging.DEBUG + + # for display options we return immediately + option_order = self.parser.get_option_order() + + self.args = args + + if self.help or self.action is None: + self._show_help(self.parser, display_options_=False) + + def _parse_command_opts(self, parser, args): + # Pull the current command from the head of the command line + command = args[0] + if not command_re.match(command): + raise SystemExit("invalid command name %r" % (command,)) + self.commands.append(command) + + # Dig up the command class that implements this command, so we + # 1) know that it's a valid command, and 2) know which options + # it takes. + try: + cmd_class = get_command_class(command) + except PackagingModuleError as msg: + raise PackagingArgError(msg) + + # XXX We want to push this in packaging.command + # + # Require that the command class be derived from Command -- want + # to be sure that the basic "command" interface is implemented. + for meth in ('initialize_options', 'finalize_options', 'run'): + if hasattr(cmd_class, meth): + continue + raise PackagingClassError( + 'command %r must implement %r' % (cmd_class, meth)) + + # Also make sure that the command object provides a list of its + # known options. + if not (hasattr(cmd_class, 'user_options') and + isinstance(cmd_class.user_options, list)): + raise PackagingClassError( + "command class %s must provide " + "'user_options' attribute (a list of tuples)" % cmd_class) + + # If the command class has a list of negative alias options, + # merge it in with the global negative aliases. + _negative_opt = negative_opt.copy() + + if hasattr(cmd_class, 'negative_opt'): + _negative_opt.update(cmd_class.negative_opt) + + # Check for help_options in command class. They have a different + # format (tuple of four) so we need to preprocess them here. + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_options = cmd_class.help_options[:] + else: + help_options = [] + + # All commands support the global options too, just by adding + # in 'global_options'. + parser.set_option_table(global_options + + cmd_class.user_options + + help_options) + parser.set_negative_aliases(_negative_opt) + args, opts = parser.getopt(args[1:]) + + if hasattr(opts, 'help') and opts.help: + self._show_command_help(cmd_class) + return + + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_option_found = False + for help_option, short, desc, func in cmd_class.help_options: + if hasattr(opts, help_option.replace('-', '_')): + help_option_found = True + if hasattr(func, '__call__'): + func() + else: + raise PackagingClassError( + "invalid help function %r for help option %r: " + "must be a callable object (function, etc.)" + % (func, help_option)) + + if help_option_found: + return + + # Put the options from the command line into their official + # holding pen, the 'command_options' dictionary. + opt_dict = self.get_option_dict(command) + for name, value in vars(opts).items(): + opt_dict[name] = ("command line", value) + + return args + + def get_option_dict(self, command): + """Get the option dictionary for a given command. If that + command's option dictionary hasn't been created yet, then create it + and return the new dictionary; otherwise, return the existing + option dictionary. + """ + d = self.command_options.get(command) + if d is None: + d = self.command_options[command] = {} + return d + + def show_help(self): + self._show_help(self.parser) + + def print_usage(self, parser): + parser.set_option_table(global_options) + + actions_ = [' %s: %s' % (name, desc) for name, desc, __ in actions] + usage = common_usage % {'actions': '\n'.join(actions_)} + + parser.print_help(usage + "\nGlobal options:") + + def _show_help(self, parser, global_options_=True, display_options_=True, + commands=[]): + # late import because of mutual dependence between these modules + from packaging.command.cmd import Command + + print('Usage: pysetup [options] action [action_options]') + print('') + if global_options_: + self.print_usage(self.parser) + print('') + + if display_options_: + parser.set_option_table(display_options) + parser.print_help( + "Information display options (just display " + + "information, ignore any commands)") + print('') + + for command in commands: + if isinstance(command, type) and issubclass(command, Command): + cls = command + else: + cls = get_command_class(command) + if (hasattr(cls, 'help_options') and + isinstance(cls.help_options, list)): + parser.set_option_table(cls.user_options + cls.help_options) + else: + parser.set_option_table(cls.user_options) + + parser.print_help("Options for %r command:" % cls.__name__) + print('') + + def _show_command_help(self, command): + if isinstance(command, str): + command = get_command_class(command) + + name = command.get_command_name() + + desc = getattr(command, 'description', '(no description available)') + print('Description: %s' % desc) + print('') + + if (hasattr(command, 'help_options') and + isinstance(command.help_options, list)): + self.parser.set_option_table(command.user_options + + command.help_options) + else: + self.parser.set_option_table(command.user_options) + + self.parser.print_help("Options:") + print('') + + def _get_command_groups(self): + """Helper function to retrieve all the command class names divided + into standard commands (listed in + packaging.command.STANDARD_COMMANDS) and extra commands (given in + self.cmdclass and not standard commands). + """ + extra_commands = [cmd for cmd in self.cmdclass + if cmd not in STANDARD_COMMANDS] + return STANDARD_COMMANDS, extra_commands + + def print_commands(self): + """Print out a help message listing all available commands with a + description of each. The list is divided into standard commands + (listed in packaging.command.STANDARD_COMMANDS) and extra commands + (given in self.cmdclass and not standard commands). The + descriptions come from the command class attribute + 'description'. + """ + std_commands, extra_commands = self._get_command_groups() + max_length = max(len(command) + for commands in (std_commands, extra_commands) + for command in commands) + + self.print_command_list(std_commands, "Standard commands", max_length) + if extra_commands: + print() + self.print_command_list(extra_commands, "Extra commands", + max_length) + + def print_command_list(self, commands, header, max_length): + """Print a subset of the list of all commands -- used by + 'print_commands()'. + """ + print(header + ":") + + for cmd in commands: + cls = self.cmdclass.get(cmd) or get_command_class(cmd) + description = getattr(cls, 'description', + '(no description available)') + + print(" %-*s %s" % (max_length, cmd, description)) + + def __call__(self): + if self.action is None: + return + for action, desc, func in actions: + if action == self.action: + return func(self, self.args) + return -1 + + +def main(args=None): + dispatcher = Dispatcher(args) + if dispatcher.action is None: + return + + return dispatcher() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Lib/packaging/tests/LONG_DESC.txt b/Lib/packaging/tests/LONG_DESC.txt new file mode 100644 index 0000000000..2b4358aae6 --- /dev/null +++ b/Lib/packaging/tests/LONG_DESC.txt @@ -0,0 +1,44 @@ +CLVault +======= + +CLVault uses Keyring to provide a command-line utility to safely store +and retrieve passwords. + +Install it using pip or the setup.py script:: + + $ python setup.py install + + $ pip install clvault + +Once it's installed, you will have three scripts installed in your +Python scripts folder, you can use to list, store and retrieve passwords:: + + $ clvault-set blog + Set your password: + Set the associated username (can be blank): tarek + Set a description (can be blank): My blog password + Password set. + + $ clvault-get blog + The username is "tarek" + The password has been copied in your clipboard + + $ clvault-list + Registered services: + blog My blog password + + +*clvault-set* takes a service name then prompt you for a password, and some +optional information about your service. The password is safely stored in +a keyring while the description is saved in a ``.clvault`` file in your +home directory. This file is created automatically the first time the command +is used. + +*clvault-get* copies the password for a given service in your clipboard, and +displays the associated user if any. + +*clvault-list* lists all registered services, with their description when +given. + + +Project page: http://bitbucket.org/tarek/clvault diff --git a/Lib/packaging/tests/PKG-INFO b/Lib/packaging/tests/PKG-INFO new file mode 100644 index 0000000000..f48546e5a8 --- /dev/null +++ b/Lib/packaging/tests/PKG-INFO @@ -0,0 +1,57 @@ +Metadata-Version: 1.2 +Name: CLVault +Version: 0.5 +Summary: Command-Line utility to store and retrieve passwords +Home-page: http://bitbucket.org/tarek/clvault +Author: Tarek Ziade +Author-email: tarek@ziade.org +License: PSF +Keywords: keyring,password,crypt +Requires-Dist: foo; sys.platform == 'okook' +Requires-Dist: bar; sys.platform == '%s' +Platform: UNKNOWN +Description: CLVault + |======= + | + |CLVault uses Keyring to provide a command-line utility to safely store + |and retrieve passwords. + | + |Install it using pip or the setup.py script:: + | + | $ python setup.py install + | + | $ pip install clvault + | + |Once it's installed, you will have three scripts installed in your + |Python scripts folder, you can use to list, store and retrieve passwords:: + | + | $ clvault-set blog + | Set your password: + | Set the associated username (can be blank): tarek + | Set a description (can be blank): My blog password + | Password set. + | + | $ clvault-get blog + | The username is "tarek" + | The password has been copied in your clipboard + | + | $ clvault-list + | Registered services: + | blog My blog password + | + | + |*clvault-set* takes a service name then prompt you for a password, and some + |optional information about your service. The password is safely stored in + |a keyring while the description is saved in a ``.clvault`` file in your + |home directory. This file is created automatically the first time the command + |is used. + | + |*clvault-get* copies the password for a given service in your clipboard, and + |displays the associated user if any. + | + |*clvault-list* lists all registered services, with their description when + |given. + | + | + |Project page: http://bitbucket.org/tarek/clvault + | diff --git a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO new file mode 100644 index 0000000000..dff8d00567 --- /dev/null +++ b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO @@ -0,0 +1,182 @@ +Metadata-Version: 1.0 +Name: setuptools +Version: 0.6c9 +Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! +Home-page: http://pypi.python.org/pypi/setuptools +Author: Phillip J. Eby +Author-email: distutils-sig@python.org +License: PSF or ZPL +Description: =============================== + Installing and Using Setuptools + =============================== + + .. contents:: **Table of Contents** + + + ------------------------- + Installation Instructions + ------------------------- + + Windows + ======= + + Install setuptools using the provided ``.exe`` installer. If you've previously + installed older versions of setuptools, please delete all ``setuptools*.egg`` + and ``setuptools.pth`` files from your system's ``site-packages`` directory + (and any other ``sys.path`` directories) FIRST. + + If you are upgrading a previous version of setuptools that was installed using + an ``.exe`` installer, please be sure to also *uninstall that older version* + via your system's "Add/Remove Programs" feature, BEFORE installing the newer + version. + + Once installation is complete, you will find an ``easy_install.exe`` program in + your Python ``Scripts`` subdirectory. Be sure to add this directory to your + ``PATH`` environment variable, if you haven't already done so. + + + RPM-Based Systems + ================= + + Install setuptools using the provided source RPM. The included ``.spec`` file + assumes you are installing using the default ``python`` executable, and is not + specific to a particular Python version. The ``easy_install`` executable will + be installed to a system ``bin`` directory such as ``/usr/bin``. + + If you wish to install to a location other than the default Python + installation's default ``site-packages`` directory (and ``$prefix/bin`` for + scripts), please use the ``.egg``-based installation approach described in the + following section. + + + Cygwin, Mac OS X, Linux, Other + ============================== + + 1. Download the appropriate egg for your version of Python (e.g. + ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. + + 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. + Setuptools will install itself using the matching version of Python (e.g. + ``python2.4``), and will place the ``easy_install`` executable in the + default location for installing Python scripts (as determined by the + standard distutils configuration files, or by the Python installation). + + If you want to install setuptools to somewhere other than ``site-packages`` or + your default distutils installation locations for libraries and scripts, you + may include EasyInstall command-line options such as ``--prefix``, + ``--install-dir``, and so on, following the ``.egg`` filename on the same + command line. For example:: + + sh setuptools-0.6c9-py2.4.egg --prefix=~ + + You can use ``--help`` to get a full options list, but we recommend consulting + the `EasyInstall manual`_ for detailed instructions, especially `the section + on custom installation locations`_. + + .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations + + + Cygwin Note + ----------- + + If you are trying to install setuptools for the **Windows** version of Python + (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make + sure that an appropriate executable (``python2.3``, ``python2.4``, or + ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For + example, doing the following at a Cygwin bash prompt will install setuptools + for the **Windows** Python found at ``C:\\Python24``:: + + ln -s /cygdrive/c/Python24/python.exe python2.4 + PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg + rm python2.4 + + + Downloads + ========= + + All setuptools downloads can be found at `the project's home page in the Python + Package Index`_. Scroll to the very bottom of the page to find the links. + + .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools + + In addition to the PyPI downloads, the development version of ``setuptools`` + is available from the `Python SVN sandbox`_, and in-development versions of the + `0.6 branch`_ are available as well. + + .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 + + .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev + + -------------------------------- + Using Setuptools and EasyInstall + -------------------------------- + + Here are some of the available manuals, tutorials, and other resources for + learning about Setuptools, Python Eggs, and EasyInstall: + + * `The EasyInstall user's guide and reference manual`_ + * `The setuptools Developer's Guide`_ + * `The pkg_resources API reference`_ + * `Package Compatibility Notes`_ (user-maintained) + * `The Internal Structure of Python Eggs`_ + + Questions, comments, and bug reports should be directed to the `distutils-sig + mailing list`_. If you have written (or know of) any tutorials, documentation, + plug-ins, or other resources for setuptools users, please let us know about + them there, so this reference list can be updated. If you have working, + *tested* patches to correct problems or add features, you may submit them to + the `setuptools bug tracker`_. + + .. _setuptools bug tracker: http://bugs.python.org/setuptools/ + .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes + .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats + .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools + .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources + .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ + + + ------- + Credits + ------- + + * The original design for the ``.egg`` format and the ``pkg_resources`` API was + co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first + version of ``pkg_resources``, and supplied the OS X operating system version + compatibility algorithm. + + * Ian Bicking implemented many early "creature comfort" features of + easy_install, including support for downloading via Sourceforge and + Subversion repositories. Ian's comments on the Web-SIG about WSGI + application deployment also inspired the concept of "entry points" in eggs, + and he has given talks at PyCon and elsewhere to inform and educate the + community about eggs and setuptools. + + * Jim Fulton contributed time and effort to build automated tests of various + aspects of ``easy_install``, and supplied the doctests for the command-line + ``.exe`` wrappers on Windows. + + * Phillip J. Eby is the principal author and maintainer of setuptools, and + first proposed the idea of an importable binary distribution format for + Python application plug-ins. + + * Significant parts of the implementation of setuptools were funded by the Open + Source Applications Foundation, to provide a plug-in infrastructure for the + Chandler PIM application. In addition, many OSAF staffers (such as Mike + "Code Bear" Taylor) contributed their time and stress as guinea pigs for the + use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) + + +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities diff --git a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 new file mode 100644 index 0000000000..4b3906a41b --- /dev/null +++ b/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 @@ -0,0 +1,183 @@ +Metadata-Version: 1.1 +Name: setuptools +Version: 0.6c9 +Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! +Home-page: http://pypi.python.org/pypi/setuptools +Author: Phillip J. Eby +Author-email: distutils-sig@python.org +License: PSF or ZPL +Description: =============================== + Installing and Using Setuptools + =============================== + + .. contents:: **Table of Contents** + + + ------------------------- + Installation Instructions + ------------------------- + + Windows + ======= + + Install setuptools using the provided ``.exe`` installer. If you've previously + installed older versions of setuptools, please delete all ``setuptools*.egg`` + and ``setuptools.pth`` files from your system's ``site-packages`` directory + (and any other ``sys.path`` directories) FIRST. + + If you are upgrading a previous version of setuptools that was installed using + an ``.exe`` installer, please be sure to also *uninstall that older version* + via your system's "Add/Remove Programs" feature, BEFORE installing the newer + version. + + Once installation is complete, you will find an ``easy_install.exe`` program in + your Python ``Scripts`` subdirectory. Be sure to add this directory to your + ``PATH`` environment variable, if you haven't already done so. + + + RPM-Based Systems + ================= + + Install setuptools using the provided source RPM. The included ``.spec`` file + assumes you are installing using the default ``python`` executable, and is not + specific to a particular Python version. The ``easy_install`` executable will + be installed to a system ``bin`` directory such as ``/usr/bin``. + + If you wish to install to a location other than the default Python + installation's default ``site-packages`` directory (and ``$prefix/bin`` for + scripts), please use the ``.egg``-based installation approach described in the + following section. + + + Cygwin, Mac OS X, Linux, Other + ============================== + + 1. Download the appropriate egg for your version of Python (e.g. + ``setuptools-0.6c9-py2.4.egg``). Do NOT rename it. + + 2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. + Setuptools will install itself using the matching version of Python (e.g. + ``python2.4``), and will place the ``easy_install`` executable in the + default location for installing Python scripts (as determined by the + standard distutils configuration files, or by the Python installation). + + If you want to install setuptools to somewhere other than ``site-packages`` or + your default distutils installation locations for libraries and scripts, you + may include EasyInstall command-line options such as ``--prefix``, + ``--install-dir``, and so on, following the ``.egg`` filename on the same + command line. For example:: + + sh setuptools-0.6c9-py2.4.egg --prefix=~ + + You can use ``--help`` to get a full options list, but we recommend consulting + the `EasyInstall manual`_ for detailed instructions, especially `the section + on custom installation locations`_. + + .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations + + + Cygwin Note + ----------- + + If you are trying to install setuptools for the **Windows** version of Python + (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make + sure that an appropriate executable (``python2.3``, ``python2.4``, or + ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg. For + example, doing the following at a Cygwin bash prompt will install setuptools + for the **Windows** Python found at ``C:\\Python24``:: + + ln -s /cygdrive/c/Python24/python.exe python2.4 + PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg + rm python2.4 + + + Downloads + ========= + + All setuptools downloads can be found at `the project's home page in the Python + Package Index`_. Scroll to the very bottom of the page to find the links. + + .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools + + In addition to the PyPI downloads, the development version of ``setuptools`` + is available from the `Python SVN sandbox`_, and in-development versions of the + `0.6 branch`_ are available as well. + + .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 + + .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev + + -------------------------------- + Using Setuptools and EasyInstall + -------------------------------- + + Here are some of the available manuals, tutorials, and other resources for + learning about Setuptools, Python Eggs, and EasyInstall: + + * `The EasyInstall user's guide and reference manual`_ + * `The setuptools Developer's Guide`_ + * `The pkg_resources API reference`_ + * `Package Compatibility Notes`_ (user-maintained) + * `The Internal Structure of Python Eggs`_ + + Questions, comments, and bug reports should be directed to the `distutils-sig + mailing list`_. If you have written (or know of) any tutorials, documentation, + plug-ins, or other resources for setuptools users, please let us know about + them there, so this reference list can be updated. If you have working, + *tested* patches to correct problems or add features, you may submit them to + the `setuptools bug tracker`_. + + .. _setuptools bug tracker: http://bugs.python.org/setuptools/ + .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes + .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats + .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools + .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources + .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ + + + ------- + Credits + ------- + + * The original design for the ``.egg`` format and the ``pkg_resources`` API was + co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first + version of ``pkg_resources``, and supplied the OS X operating system version + compatibility algorithm. + + * Ian Bicking implemented many early "creature comfort" features of + easy_install, including support for downloading via Sourceforge and + Subversion repositories. Ian's comments on the Web-SIG about WSGI + application deployment also inspired the concept of "entry points" in eggs, + and he has given talks at PyCon and elsewhere to inform and educate the + community about eggs and setuptools. + + * Jim Fulton contributed time and effort to build automated tests of various + aspects of ``easy_install``, and supplied the doctests for the command-line + ``.exe`` wrappers on Windows. + + * Phillip J. Eby is the principal author and maintainer of setuptools, and + first proposed the idea of an importable binary distribution format for + Python application plug-ins. + + * Significant parts of the implementation of setuptools were funded by the Open + Source Applications Foundation, to provide a plug-in infrastructure for the + Chandler PIM application. In addition, many OSAF staffers (such as Mike + "Code Bear" Taylor) contributed their time and stress as guinea pigs for the + use of eggs and setuptools, even before eggs were "cool". (Thanks, guys!) + + +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires: Foo diff --git a/Lib/packaging/tests/__init__.py b/Lib/packaging/tests/__init__.py new file mode 100644 index 0000000000..0b0e3c5543 --- /dev/null +++ b/Lib/packaging/tests/__init__.py @@ -0,0 +1,133 @@ +"""Test suite for packaging. + +This test suite consists of a collection of test modules in the +packaging.tests package. Each test module has a name starting with +'test' and contains a function test_suite(). The function is expected +to return an initialized unittest.TestSuite instance. + +Utility code is included in packaging.tests.support. +""" + +# Put this text back for the backport +#Always import unittest from this module, it will be the right version +#(standard library unittest for 3.2 and higher, third-party unittest2 +#elease for older versions). + +import os +import sys +import unittest +from test.support import TESTFN + +# XXX move helpers to support, add tests for them, remove things that +# duplicate test.support (or keep them for the backport; needs thinking) + +here = os.path.dirname(__file__) or os.curdir +verbose = 1 + +def test_suite(): + suite = unittest.TestSuite() + for fn in os.listdir(here): + if fn.startswith("test") and fn.endswith(".py"): + modname = "packaging.tests." + fn[:-3] + __import__(modname) + module = sys.modules[modname] + suite.addTest(module.test_suite()) + return suite + + +class Error(Exception): + """Base class for regression test exceptions.""" + + +class TestFailed(Error): + """Test failed.""" + + +class BasicTestRunner: + def run(self, test): + result = unittest.TestResult() + test(result) + return result + + +def _run_suite(suite, verbose_=1): + """Run tests from a unittest.TestSuite-derived class.""" + global verbose + verbose = verbose_ + if verbose_: + runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + else: + runner = BasicTestRunner() + + result = runner.run(suite) + if not result.wasSuccessful(): + if len(result.errors) == 1 and not result.failures: + err = result.errors[0][1] + elif len(result.failures) == 1 and not result.errors: + err = result.failures[0][1] + else: + err = "errors occurred; run in verbose mode for details" + raise TestFailed(err) + + +def run_unittest(classes, verbose_=1): + """Run tests from unittest.TestCase-derived classes. + + Originally extracted from stdlib test.test_support and modified to + support unittest2. + """ + valid_types = (unittest.TestSuite, unittest.TestCase) + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, str): + if cls in sys.modules: + suite.addTest(unittest.findTestCases(sys.modules[cls])) + else: + raise ValueError("str arguments must be keys in sys.modules") + elif isinstance(cls, valid_types): + suite.addTest(cls) + else: + suite.addTest(unittest.makeSuite(cls)) + _run_suite(suite, verbose_) + + +def reap_children(): + """Use this function at the end of test_main() whenever sub-processes + are started. This will help ensure that no extra children (zombies) + stick around to hog resources and create problems when looking + for refleaks. + + Extracted from stdlib test.support. + """ + + # Reap all our dead child processes so we don't leave zombies around. + # These hog resources and might be causing some of the buildbots to die. + if hasattr(os, 'waitpid'): + any_process = -1 + while True: + try: + # This will raise an exception on Windows. That's ok. + pid, status = os.waitpid(any_process, os.WNOHANG) + if pid == 0: + break + except: + break + + +def captured_stdout(func, *args, **kw): + import io + orig_stdout = getattr(sys, 'stdout') + setattr(sys, 'stdout', io.StringIO()) + try: + res = func(*args, **kw) + sys.stdout.seek(0) + return res, sys.stdout.read() + finally: + setattr(sys, 'stdout', orig_stdout) + + +def unload(name): + try: + del sys.modules[name] + except KeyError: + pass diff --git a/Lib/packaging/tests/__main__.py b/Lib/packaging/tests/__main__.py new file mode 100644 index 0000000000..68ee229e5a --- /dev/null +++ b/Lib/packaging/tests/__main__.py @@ -0,0 +1,20 @@ +"""Packaging test suite runner.""" + +# Ripped from importlib tests, thanks Brett! + +import os +import sys +import unittest +from test.support import run_unittest, reap_children + + +def test_main(): + start_dir = os.path.dirname(__file__) + top_dir = os.path.dirname(os.path.dirname(start_dir)) + test_loader = unittest.TestLoader() + run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir)) + reap_children() + + +if __name__ == '__main__': + test_main() diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA new file mode 100644 index 0000000000..65e839a01f --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA @@ -0,0 +1,4 @@ +Metadata-version: 1.2 +Name: babar +Version: 0.1 +Author: FELD Boris \ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES new file mode 100644 index 0000000000..5d0da494a5 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES @@ -0,0 +1,2 @@ +babar.png,babar.png +babar.cfg,babar.cfg \ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar.cfg b/Lib/packaging/tests/fake_dists/babar.cfg new file mode 100644 index 0000000000..ecd6efe9a6 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/babar.cfg @@ -0,0 +1 @@ +Config \ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/babar.png b/Lib/packaging/tests/fake_dists/babar.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO b/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO new file mode 100644 index 0000000000..a176dfdfde --- /dev/null +++ b/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO @@ -0,0 +1,6 @@ +Metadata-Version: 1.2 +Name: bacon +Version: 0.1 +Provides-Dist: truffles (2.0) +Provides-Dist: bacon (0.1) +Obsoletes-Dist: truffles (>=0.9,<=1.5) diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO new file mode 100644 index 0000000000..a7e118a88f --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,18 @@ +Metadata-Version: 1.0 +Name: banana +Version: 0.4 +Summary: A yellow fruit +Home-page: http://en.wikipedia.org/wiki/Banana +Author: Josip Djolonga +Author-email: foo@nbar.com +License: BSD +Description: A fruit +Keywords: foo bar +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Scientific/Engineering :: GIS diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt new file mode 100644 index 0000000000..5d3e5f68c7 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt @@ -0,0 +1,3 @@ + + # -*- Entry points: -*- + \ No newline at end of file diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe @@ -0,0 +1 @@ + diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt new file mode 100644 index 0000000000..4354305659 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt @@ -0,0 +1,6 @@ +# this should be ignored + +strawberry >=0.5 + +[section ignored] +foo ==0.5 diff --git a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt b/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info b/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info new file mode 100644 index 0000000000..27cbe3014d --- /dev/null +++ b/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: cheese +Version: 2.0.2 +Provides-Dist: truffles (1.0.2) +Obsoletes-Dist: truffles (!=1.2,<=2.0) diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA new file mode 100644 index 0000000000..418929ecca --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA @@ -0,0 +1,9 @@ +Metadata-Version: 1.2 +Name: choxie +Version: 2.0.0.9 +Summary: Chocolate with a kick! +Requires-Dist: towel-stuff (0.1) +Requires-Dist: nut +Provides-Dist: truffles (1.0) +Obsoletes-Dist: truffles (<=0.8,>=0.5) +Obsoletes-Dist: truffles (<=0.9,>=0.6) diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py new file mode 100644 index 0000000000..40a96afc6f --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py new file mode 100644 index 0000000000..c4027f36c1 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from towel_stuff import Towel + +class Chocolate(object): + """A piece of chocolate.""" + + def wrap_with_towel(self): + towel = Towel() + towel.wrap(self) + return towel diff --git a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py new file mode 100644 index 0000000000..342b8ea851 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from choxie.chocolate import Chocolate + +class Truffle(Chocolate): + """A truffle.""" diff --git a/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO b/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO new file mode 100644 index 0000000000..499a083e40 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: coconuts-aster +Version: 10.3 +Provides-Dist: strawberry (0.6) +Provides-Dist: banana (0.4) diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA new file mode 100644 index 0000000000..0b99f5249a --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: grammar +Version: 1.0a4 +Requires-Dist: truffles (>=1.2) +Author: Sherlock Holmes diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py new file mode 100644 index 0000000000..40a96afc6f --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py new file mode 100644 index 0000000000..66ba796c3d --- /dev/null +++ b/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from random import randint + +def is_valid_grammar(sentence): + if randint(0, 10) < 2: + return False + else: + return True diff --git a/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info b/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info new file mode 100644 index 0000000000..0c58ec1ce9 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info @@ -0,0 +1,3 @@ +Metadata-Version: 1.2 +Name: nut +Version: funkyversion diff --git a/Lib/packaging/tests/fake_dists/strawberry-0.6.egg b/Lib/packaging/tests/fake_dists/strawberry-0.6.egg new file mode 100644 index 0000000000000000000000000000000000000000..6d160e8b161031ae52638514843592187925b757 GIT binary patch literal 1402 zcmY#Z)KALH(=X28%1l#;R!B%nEKbc!%uQ8LF-TCbRZuEUEh#N1$!5UyyB9?oE!zH`APZtCB-F0i3JcFQY$h`G~i+ynwm-qN-7RYT&X$5say&QsmU4n z3MvK)nZ*iu`6UV^8L0}%`9(#k$t4P4FQ?@fDU@Vn7AvHtrz@mo=A`PuGzH|OCKjhE z6hVR}vqT{&F*#eIBp=D07{Q~En3tkZQdFssn4XxK2Q~^6PHBlC$E0O)1$Z;Fa4~>D z+4TK3AR30n7-Se+-Q9IP{oMTZOY#fib5hGvbM#6oNxFshKc^kq8?ahzxAbc>w~0P49=HE>`e1Wyb<_dzQq3LzE;$zO zInAp+Nijh`?zmR7x!%W>XS%27_imWXogS~&znpi~!{3GMua{n5a%KPY@&nrECr70<2ZYf=17sv9U>O*`1cX>m zX1i|0$iTn=!m@fv08icnr79tyuE#3mWS=m5J QSs7Rvwlgv?tYrrA0Pqom)c^nh literal 0 HcmV?d00001 diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA new file mode 100644 index 0000000000..ca46d0a4ea --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA @@ -0,0 +1,7 @@ +Metadata-Version: 1.2 +Name: towel-stuff +Version: 0.1 +Provides-Dist: truffles (1.1.2) +Provides-Dist: towel-stuff (0.1) +Obsoletes-Dist: truffles (!=0.8,<1.0) +Requires-Dist: bacon (<=0.2) diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED b/Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py b/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py new file mode 100644 index 0000000000..191f895b0a --- /dev/null +++ b/Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +class Towel(object): + """A towel, that one should never be without.""" + + def __init__(self, color='tie-dye'): + self.color = color + self.wrapped_obj = None + + def wrap(self, obj): + """Wrap an object up in our towel.""" + self.wrapped_obj = obj + + def unwrap(self): + """Unwrap whatever is in our towel and return whatever it is.""" + obj = self.wrapped_obj + self.wrapped_obj = None + return obj diff --git a/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info b/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info new file mode 100644 index 0000000000..45f0cf8b39 --- /dev/null +++ b/Lib/packaging/tests/fake_dists/truffles-5.0.egg-info @@ -0,0 +1,3 @@ +Metadata-Version: 1.2 +Name: truffles +Version: 5.0 diff --git a/Lib/packaging/tests/fixer/__init__.py b/Lib/packaging/tests/fixer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/fixer/fix_idioms.py b/Lib/packaging/tests/fixer/fix_idioms.py new file mode 100644 index 0000000000..64f5ea0516 --- /dev/null +++ b/Lib/packaging/tests/fixer/fix_idioms.py @@ -0,0 +1,134 @@ +"""Adjust some old Python 2 idioms to their modern counterparts. + +* Change some type comparisons to isinstance() calls: + type(x) == T -> isinstance(x, T) + type(x) is T -> isinstance(x, T) + type(x) != T -> not isinstance(x, T) + type(x) is not T -> not isinstance(x, T) + +* Change "while 1:" into "while True:". + +* Change both + + v = list(EXPR) + v.sort() + foo(v) + +and the more general + + v = EXPR + v.sort() + foo(v) + +into + + v = sorted(EXPR) + foo(v) +""" +# Author: Jacques Frechet, Collin Winter + +# Local imports +from lib2to3 import fixer_base +from lib2to3.fixer_util import Call, Comma, Name, Node, syms + +CMP = "(n='!=' | '==' | 'is' | n=comp_op< 'is' 'not' >)" +TYPE = "power< 'type' trailer< '(' x=any ')' > >" + +class FixIdioms(fixer_base.BaseFix): + + explicit = False # The user must ask for this fixer + + PATTERN = r""" + isinstance=comparison< %s %s T=any > + | + isinstance=comparison< T=any %s %s > + | + while_stmt< 'while' while='1' ':' any+ > + | + sorted=any< + any* + simple_stmt< + expr_stmt< id1=any '=' + power< list='list' trailer< '(' (not arglist) any ')' > > + > + '\n' + > + sort= + simple_stmt< + power< id2=any + trailer< '.' 'sort' > trailer< '(' ')' > + > + '\n' + > + next=any* + > + | + sorted=any< + any* + simple_stmt< expr_stmt< id1=any '=' expr=any > '\n' > + sort= + simple_stmt< + power< id2=any + trailer< '.' 'sort' > trailer< '(' ')' > + > + '\n' + > + next=any* + > + """ % (TYPE, CMP, CMP, TYPE) + + def match(self, node): + r = super(FixIdioms, self).match(node) + # If we've matched one of the sort/sorted subpatterns above, we + # want to reject matches where the initial assignment and the + # subsequent .sort() call involve different identifiers. + if r and "sorted" in r: + if r["id1"] == r["id2"]: + return r + return None + return r + + def transform(self, node, results): + if "isinstance" in results: + return self.transform_isinstance(node, results) + elif "while" in results: + return self.transform_while(node, results) + elif "sorted" in results: + return self.transform_sort(node, results) + else: + raise RuntimeError("Invalid match") + + def transform_isinstance(self, node, results): + x = results["x"].clone() # The thing inside of type() + T = results["T"].clone() # The type being compared against + x.prefix = "" + T.prefix = " " + test = Call(Name("isinstance"), [x, Comma(), T]) + if "n" in results: + test.prefix = " " + test = Node(syms.not_test, [Name("not"), test]) + test.prefix = node.prefix + return test + + def transform_while(self, node, results): + one = results["while"] + one.replace(Name("True", prefix=one.prefix)) + + def transform_sort(self, node, results): + sort_stmt = results["sort"] + next_stmt = results["next"] + list_call = results.get("list") + simple_expr = results.get("expr") + + if list_call: + list_call.replace(Name("sorted", prefix=list_call.prefix)) + elif simple_expr: + new = simple_expr.clone() + new.prefix = "" + simple_expr.replace(Call(Name("sorted"), [new], + prefix=simple_expr.prefix)) + else: + raise RuntimeError("should not have reached here") + sort_stmt.remove() + if next_stmt: + next_stmt[0].prefix = sort_stmt._prefix diff --git a/Lib/packaging/tests/pypi_server.py b/Lib/packaging/tests/pypi_server.py new file mode 100644 index 0000000000..cc5fcca05e --- /dev/null +++ b/Lib/packaging/tests/pypi_server.py @@ -0,0 +1,444 @@ +"""Mock PyPI Server implementation, to use in tests. + +This module also provides a simple test case to extend if you need to use +the PyPIServer all along your test case. Be sure to read the documentation +before any use. + +XXX TODO: + +The mock server can handle simple HTTP request (to simulate a simple index) or +XMLRPC requests, over HTTP. Both does not have the same intergface to deal +with, and I think it's a pain. + +A good idea could be to re-think a bit the way dstributions are handled in the +mock server. As it should return malformed HTML pages, we need to keep the +static behavior. + +I think of something like that: + + >>> server = PyPIMockServer() + >>> server.startHTTP() + >>> server.startXMLRPC() + +Then, the server must have only one port to rely on, eg. + + >>> server.fulladress() + "http://ip:port/" + +It could be simple to have one HTTP server, relaying the requests to the two +implementations (static HTTP and XMLRPC over HTTP). +""" + +import os +import queue +import select +import socket +import threading +import socketserver +from functools import wraps +from http.server import HTTPServer, SimpleHTTPRequestHandler +from xmlrpc.server import SimpleXMLRPCServer + +from packaging.tests import unittest + +PYPI_DEFAULT_STATIC_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'pypiserver') + + +def use_xmlrpc_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = True + return use_pypi_server(*server_args, **server_kwargs) + + +def use_http_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = False + return use_pypi_server(*server_args, **server_kwargs) + + +def use_pypi_server(*server_args, **server_kwargs): + """Decorator to make use of the PyPIServer for test methods, + just when needed, and not for the entire duration of the testcase. + """ + def wrapper(func): + @wraps(func) + def wrapped(*args, **kwargs): + server = PyPIServer(*server_args, **server_kwargs) + server.start() + try: + func(server=server, *args, **kwargs) + finally: + server.stop() + return wrapped + return wrapper + + +class PyPIServerTestCase(unittest.TestCase): + + def setUp(self): + super(PyPIServerTestCase, self).setUp() + self.pypi = PyPIServer() + self.pypi.start() + self.addCleanup(self.pypi.stop) + + +class PyPIServer(threading.Thread): + """PyPI Mocked server. + Provides a mocked version of the PyPI API's, to ease tests. + + Support serving static content and serving previously given text. + """ + + def __init__(self, test_static_path=None, + static_filesystem_paths=["default"], + static_uri_paths=["simple", "packages"], serve_xmlrpc=False): + """Initialize the server. + + Default behavior is to start the HTTP server. You can either start the + xmlrpc server by setting xmlrpc to True. Caution: Only one server will + be started. + + static_uri_paths and static_base_path are parameters used to provides + respectively the http_paths to serve statically, and where to find the + matching files on the filesystem. + """ + # we want to launch the server in a new dedicated thread, to not freeze + # tests. + threading.Thread.__init__(self) + self._run = True + self._serve_xmlrpc = serve_xmlrpc + + #TODO allow to serve XMLRPC and HTTP static files at the same time. + if not self._serve_xmlrpc: + self.server = HTTPServer(('127.0.0.1', 0), PyPIRequestHandler) + self.server.RequestHandlerClass.pypi_server = self + + self.request_queue = queue.Queue() + self._requests = [] + self.default_response_status = 404 + self.default_response_headers = [('Content-type', 'text/plain')] + self.default_response_data = "The page does not exists" + + # initialize static paths / filesystems + self.static_uri_paths = static_uri_paths + + # append the static paths defined locally + if test_static_path is not None: + static_filesystem_paths.append(test_static_path) + self.static_filesystem_paths = [ + PYPI_DEFAULT_STATIC_PATH + "/" + path + for path in static_filesystem_paths] + else: + # XMLRPC server + self.server = PyPIXMLRPCServer(('127.0.0.1', 0)) + self.xmlrpc = XMLRPCMockIndex() + # register the xmlrpc methods + self.server.register_introspection_functions() + self.server.register_instance(self.xmlrpc) + + self.address = (self.server.server_name, self.server.server_port) + # to not have unwanted outputs. + self.server.RequestHandlerClass.log_request = lambda *_: None + + def run(self): + # loop because we can't stop it otherwise, for python < 2.6 + while self._run: + r, w, e = select.select([self.server], [], [], 0.5) + if r: + self.server.handle_request() + + def stop(self): + """self shutdown is not supported for python < 2.6""" + self._run = False + + def get_next_response(self): + return (self.default_response_status, + self.default_response_headers, + self.default_response_data) + + @property + def requests(self): + """Use this property to get all requests that have been made + to the server + """ + while True: + try: + self._requests.append(self.request_queue.get_nowait()) + except queue.Empty: + break + return self._requests + + @property + def full_address(self): + return "http://%s:%s" % self.address + + +class PyPIRequestHandler(SimpleHTTPRequestHandler): + # we need to access the pypi server while serving the content + pypi_server = None + + def serve_request(self): + """Serve the content. + + Also record the requests to be accessed later. If trying to access an + url matching a static uri, serve static content, otherwise serve + what is provided by the `get_next_response` method. + + If nothing is defined there, return a 404 header. + """ + # record the request. Read the input only on PUT or POST requests + if self.command in ("PUT", "POST"): + if 'content-length' in self.headers: + request_data = self.rfile.read( + int(self.headers['content-length'])) + else: + request_data = self.rfile.read() + + elif self.command in ("GET", "DELETE"): + request_data = '' + + self.pypi_server.request_queue.put((self, request_data)) + + # serve the content from local disc if we request an URL beginning + # by a pattern defined in `static_paths` + url_parts = self.path.split("/") + if (len(url_parts) > 1 and + url_parts[1] in self.pypi_server.static_uri_paths): + data = None + # always take the last first. + fs_paths = [] + fs_paths.extend(self.pypi_server.static_filesystem_paths) + fs_paths.reverse() + relative_path = self.path + for fs_path in fs_paths: + try: + if self.path.endswith("/"): + relative_path += "index.html" + + if relative_path.endswith('.tar.gz'): + with open(fs_path + relative_path, 'br') as file: + data = file.read() + headers = [('Content-type', 'application/x-gtar')] + else: + with open(fs_path + relative_path) as file: + data = file.read().encode() + headers = [('Content-type', 'text/html')] + + self.make_response(data, headers=headers) + + except IOError: + pass + + if data is None: + self.make_response("Not found", 404) + + # otherwise serve the content from get_next_response + else: + # send back a response + status, headers, data = self.pypi_server.get_next_response() + self.make_response(data, status, headers) + + do_POST = do_GET = do_DELETE = do_PUT = serve_request + + def make_response(self, data, status=200, + headers=[('Content-type', 'text/html')]): + """Send the response to the HTTP client""" + if not isinstance(status, int): + try: + status = int(status) + except ValueError: + # we probably got something like YYY Codename. + # Just get the first 3 digits + status = int(status[:3]) + + self.send_response(status) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + + if type(data) is str: + data = data.encode() + + self.wfile.write(data) + + +class PyPIXMLRPCServer(SimpleXMLRPCServer): + def server_bind(self): + """Override server_bind to store the server name.""" + socketserver.TCPServer.server_bind(self) + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + +class MockDist: + """Fake distribution, used in the Mock PyPI Server""" + + def __init__(self, name, version="1.0", hidden=False, url="http://url/", + type="sdist", filename="", size=10000, + digest="123456", downloads=7, has_sig=False, + python_version="source", comment="comment", + author="John Doe", author_email="john@doe.name", + maintainer="Main Tayner", maintainer_email="maintainer_mail", + project_url="http://project_url/", homepage="http://homepage/", + keywords="", platform="UNKNOWN", classifiers=[], licence="", + description="Description", summary="Summary", stable_version="", + ordering="", documentation_id="", code_kwalitee_id="", + installability_id="", obsoletes=[], obsoletes_dist=[], + provides=[], provides_dist=[], requires=[], requires_dist=[], + requires_external=[], requires_python=""): + + # basic fields + self.name = name + self.version = version + self.hidden = hidden + + # URL infos + self.url = url + self.digest = digest + self.downloads = downloads + self.has_sig = has_sig + self.python_version = python_version + self.comment = comment + self.type = type + + # metadata + self.author = author + self.author_email = author_email + self.maintainer = maintainer + self.maintainer_email = maintainer_email + self.project_url = project_url + self.homepage = homepage + self.keywords = keywords + self.platform = platform + self.classifiers = classifiers + self.licence = licence + self.description = description + self.summary = summary + self.stable_version = stable_version + self.ordering = ordering + self.cheesecake_documentation_id = documentation_id + self.cheesecake_code_kwalitee_id = code_kwalitee_id + self.cheesecake_installability_id = installability_id + + self.obsoletes = obsoletes + self.obsoletes_dist = obsoletes_dist + self.provides = provides + self.provides_dist = provides_dist + self.requires = requires + self.requires_dist = requires_dist + self.requires_external = requires_external + self.requires_python = requires_python + + def url_infos(self): + return { + 'url': self.url, + 'packagetype': self.type, + 'filename': 'filename.tar.gz', + 'size': '6000', + 'md5_digest': self.digest, + 'downloads': self.downloads, + 'has_sig': self.has_sig, + 'python_version': self.python_version, + 'comment_text': self.comment, + } + + def metadata(self): + return { + 'maintainer': self.maintainer, + 'project_url': [self.project_url], + 'maintainer_email': self.maintainer_email, + 'cheesecake_code_kwalitee_id': self.cheesecake_code_kwalitee_id, + 'keywords': self.keywords, + 'obsoletes_dist': self.obsoletes_dist, + 'requires_external': self.requires_external, + 'author': self.author, + 'author_email': self.author_email, + 'download_url': self.url, + 'platform': self.platform, + 'version': self.version, + 'obsoletes': self.obsoletes, + 'provides': self.provides, + 'cheesecake_documentation_id': self.cheesecake_documentation_id, + '_pypi_hidden': self.hidden, + 'description': self.description, + '_pypi_ordering': 19, + 'requires_dist': self.requires_dist, + 'requires_python': self.requires_python, + 'classifiers': [], + 'name': self.name, + 'licence': self.licence, + 'summary': self.summary, + 'home_page': self.homepage, + 'stable_version': self.stable_version, + 'provides_dist': self.provides_dist or "%s (%s)" % (self.name, + self.version), + 'requires': self.requires, + 'cheesecake_installability_id': self.cheesecake_installability_id, + } + + def search_result(self): + return { + '_pypi_ordering': 0, + 'version': self.version, + 'name': self.name, + 'summary': self.summary, + } + + +class XMLRPCMockIndex: + """Mock XMLRPC server""" + + def __init__(self, dists=[]): + self._dists = dists + self._search_result = [] + + def add_distributions(self, dists): + for dist in dists: + self._dists.append(MockDist(**dist)) + + def set_distributions(self, dists): + self._dists = [] + self.add_distributions(dists) + + def set_search_result(self, result): + """set a predefined search result""" + self._search_result = result + + def _get_search_results(self): + results = [] + for name in self._search_result: + found_dist = [d for d in self._dists if d.name == name] + if found_dist: + results.append(found_dist[0]) + else: + dist = MockDist(name) + results.append(dist) + self._dists.append(dist) + return [r.search_result() for r in results] + + def list_packages(self): + return [d.name for d in self._dists] + + def package_releases(self, package_name, show_hidden=False): + if show_hidden: + # return all + return [d.version for d in self._dists if d.name == package_name] + else: + # return only un-hidden + return [d.version for d in self._dists if d.name == package_name + and not d.hidden] + + def release_urls(self, package_name, version): + return [d.url_infos() for d in self._dists + if d.name == package_name and d.version == version] + + def release_data(self, package_name, version): + release = [d for d in self._dists + if d.name == package_name and d.version == version] + if release: + return release[0].metadata() + else: + return {} + + def search(self, spec, operator="and"): + return self._get_search_results() diff --git a/Lib/packaging/tests/pypi_test_server.py b/Lib/packaging/tests/pypi_test_server.py new file mode 100644 index 0000000000..8c8c641eb0 --- /dev/null +++ b/Lib/packaging/tests/pypi_test_server.py @@ -0,0 +1,59 @@ +"""Test PyPI Server implementation at testpypi.python.org, to use in tests. + +This is a drop-in replacement for the mock pypi server for testing against a +real pypi server hosted by python.org especially for testing against. +""" + +import unittest + +PYPI_DEFAULT_STATIC_PATH = None + + +def use_xmlrpc_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = True + return use_pypi_server(*server_args, **server_kwargs) + + +def use_http_server(*server_args, **server_kwargs): + server_kwargs['serve_xmlrpc'] = False + return use_pypi_server(*server_args, **server_kwargs) + + +def use_pypi_server(*server_args, **server_kwargs): + """Decorator to make use of the PyPIServer for test methods, + just when needed, and not for the entire duration of the testcase. + """ + def wrapper(func): + def wrapped(*args, **kwargs): + server = PyPIServer(*server_args, **server_kwargs) + func(server=server, *args, **kwargs) + return wrapped + return wrapper + + +class PyPIServerTestCase(unittest.TestCase): + + def setUp(self): + super(PyPIServerTestCase, self).setUp() + self.pypi = PyPIServer() + self.pypi.start() + self.addCleanup(self.pypi.stop) + + +class PyPIServer: + """Shim to access testpypi.python.org, for testing a real server.""" + + def __init__(self, test_static_path=None, + static_filesystem_paths=["default"], + static_uri_paths=["simple"], serve_xmlrpc=False): + self.address = ('testpypi.python.org', '80') + + def start(self): + pass + + def stop(self): + pass + + @property + def full_address(self): + return "http://%s:%s" % self.address diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz b/Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..333961eb18a6e7db80fefd41c339ab218d5180c4 GIT binary patch literal 110 zcmb2|=3uy!>FUeC{PvtR-ysJc)&sVu?9yZ7`(A1Di)P(6s!I71JWZ;--fWND`LA)=lAmk-7Jbj=XMlnFEsQ#U Kd|Vkc7#IK&xGYxy literal 0 HcmV?d00001 diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html new file mode 100644 index 0000000000..b89f1bdb84 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html @@ -0,0 +1,3 @@ + +badmd5-0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html new file mode 100644 index 0000000000..9e42b16d23 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html @@ -0,0 +1,3 @@ + +foobar-0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html new file mode 100644 index 0000000000..9baee0479e --- /dev/null +++ b/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html @@ -0,0 +1,2 @@ +foobar/ +badmd5/ diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html new file mode 100644 index 0000000000..c3d42c5692 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html @@ -0,0 +1,6 @@ +Links for bar

Links for bar

+bar-1.0.tar.gz
+bar-1.0.1.tar.gz
+bar-2.0.tar.gz
+bar-2.0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html new file mode 100644 index 0000000000..4f34312a30 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html @@ -0,0 +1,6 @@ +Links for baz

Links for baz

+baz-1.0.tar.gz
+baz-1.0.1.tar.gz
+baz-2.0.tar.gz
+baz-2.0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html new file mode 100644 index 0000000000..0565e11bdd --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html @@ -0,0 +1,6 @@ +Links for foo

Links for foo

+foo-1.0.tar.gz
+foo-1.0.1.tar.gz
+foo-2.0.tar.gz
+foo-2.0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html new file mode 100644 index 0000000000..a70cfd345f --- /dev/null +++ b/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html @@ -0,0 +1,3 @@ +foo/ +bar/ +baz/ diff --git a/Lib/packaging/tests/pypiserver/project_list/simple/index.html b/Lib/packaging/tests/pypiserver/project_list/simple/index.html new file mode 100644 index 0000000000..b36d728a0b --- /dev/null +++ b/Lib/packaging/tests/pypiserver/project_list/simple/index.html @@ -0,0 +1,5 @@ +FooBar-bar +Foobar-baz +Baz-FooBar +Baz +Foo diff --git a/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html new file mode 100644 index 0000000000..a282a4ea31 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html @@ -0,0 +1,6 @@ +Links for Foobar

Links for Foobar

+Foobar-1.0.tar.gz
+Foobar-1.0.1.tar.gz
+Foobar-2.0.tar.gz
+Foobar-2.0.1.tar.gz
+ diff --git a/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html b/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html new file mode 100644 index 0000000000..a1a7bb7282 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html @@ -0,0 +1 @@ +foobar/ diff --git a/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html b/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html new file mode 100644 index 0000000000..265ee0af95 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html @@ -0,0 +1 @@ +index.html from external server diff --git a/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html b/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html new file mode 100644 index 0000000000..6f976676f5 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html @@ -0,0 +1 @@ +Yeah diff --git a/Lib/packaging/tests/pypiserver/with_externals/external/external.html b/Lib/packaging/tests/pypiserver/with_externals/external/external.html new file mode 100644 index 0000000000..92e4702f63 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/external/external.html @@ -0,0 +1,3 @@ + +bad old link + diff --git a/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html new file mode 100644 index 0000000000..b100a26542 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html @@ -0,0 +1,4 @@ + +foobar-0.1.tar.gz
+external homepage
+ diff --git a/Lib/packaging/tests/pypiserver/with_externals/simple/index.html b/Lib/packaging/tests/pypiserver/with_externals/simple/index.html new file mode 100644 index 0000000000..a1a7bb7282 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_externals/simple/index.html @@ -0,0 +1 @@ +foobar/ diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html b/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html new file mode 100644 index 0000000000..1cc0c32f1b --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html @@ -0,0 +1,7 @@ + + +

a rel=homepage HTML page

+foobar 2.0 + + + diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html b/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html new file mode 100644 index 0000000000..f6ace22054 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html @@ -0,0 +1 @@ +A page linked without rel="download" or rel="homepage" link. diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html new file mode 100644 index 0000000000..171df9360c --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html @@ -0,0 +1,6 @@ + +foobar-0.1.tar.gz
+external homepage
+unrelated link
+unrelated download
+ diff --git a/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html b/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html new file mode 100644 index 0000000000..a1a7bb7282 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html @@ -0,0 +1 @@ +foobar/ diff --git a/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html b/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html new file mode 100644 index 0000000000..b2885ae384 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html @@ -0,0 +1,4 @@ + +foobar-0.1.tar.gz
+external homepage
+ diff --git a/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html b/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html new file mode 100644 index 0000000000..a1a7bb7282 --- /dev/null +++ b/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html @@ -0,0 +1 @@ +foobar/ diff --git a/Lib/packaging/tests/support.py b/Lib/packaging/tests/support.py new file mode 100644 index 0000000000..cf5d7883a9 --- /dev/null +++ b/Lib/packaging/tests/support.py @@ -0,0 +1,259 @@ +"""Support code for packaging test cases. + +A few helper classes are provided: LoggingCatcher, TempdirManager and +EnvironRestorer. They are written to be used as mixins:: + + from packaging.tests import unittest + from packaging.tests.support import LoggingCatcher + + class SomeTestCase(LoggingCatcher, unittest.TestCase): + +If you need to define a setUp method on your test class, you have to +call the mixin class' setUp method or it won't work (same thing for +tearDown): + + def setUp(self): + super(SomeTestCase, self).setUp() + ... # other setup code + +Also provided is a DummyCommand class, useful to mock commands in the +tests of another command that needs them, a create_distribution function +and a skip_unless_symlink decorator. + +Also provided is a DummyCommand class, useful to mock commands in the +tests of another command that needs them, a create_distribution function +and a skip_unless_symlink decorator. + +Each class or function has a docstring to explain its purpose and usage. +""" + +import os +import errno +import shutil +import logging +import weakref +import tempfile + +from packaging import logger +from packaging.dist import Distribution +from packaging.tests import unittest + +__all__ = ['LoggingCatcher', 'TempdirManager', 'EnvironRestorer', + 'DummyCommand', 'unittest', 'create_distribution', + 'skip_unless_symlink'] + + +class _TestHandler(logging.handlers.BufferingHandler): + # stolen and adapted from test.support + + def __init__(self): + logging.handlers.BufferingHandler.__init__(self, 0) + self.setLevel(logging.DEBUG) + + def shouldFlush(self): + return False + + def emit(self, record): + self.buffer.append(record) + + +class LoggingCatcher: + """TestCase-compatible mixin to receive logging calls. + + Upon setUp, instances of this classes get a BufferingHandler that's + configured to record all messages logged to the 'packaging' logger. + + Use get_logs to retrieve messages and self.loghandler.flush to discard + them. + """ + + def setUp(self): + super(LoggingCatcher, self).setUp() + self.loghandler = handler = _TestHandler() + logger.addHandler(handler) + self.addCleanup(logger.setLevel, logger.level) + logger.setLevel(logging.DEBUG) # we want all messages + + def tearDown(self): + handler = self.loghandler + # All this is necessary to properly shut down the logging system and + # avoid a regrtest complaint. Thanks to Vinay Sajip for the help. + handler.close() + logger.removeHandler(handler) + for ref in weakref.getweakrefs(handler): + logging._removeHandlerRef(ref) + del self.loghandler + super(LoggingCatcher, self).tearDown() + + def get_logs(self, *levels): + """Return all log messages with level in *levels*. + + Without explicit levels given, returns all messages. + *levels* defaults to all levels. For log calls with arguments (i.e. + logger.info('bla bla %s', arg)), the messages + Returns a list. + + Example: self.get_logs(logging.WARN, logging.DEBUG). + """ + if not levels: + return [log.getMessage() for log in self.loghandler.buffer] + return [log.getMessage() for log in self.loghandler.buffer + if log.levelno in levels] + + +class TempdirManager: + """TestCase-compatible mixin to create temporary directories and files. + + Directories and files created in a test_* method will be removed after it + has run. + """ + + def setUp(self): + super(TempdirManager, self).setUp() + self._basetempdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self._basetempdir, os.name in ('nt', 'cygwin')) + super(TempdirManager, self).tearDown() + + def mktempfile(self): + """Create a read-write temporary file and return it.""" + + def _delete_file(filename): + try: + os.remove(filename) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + + fd, fn = tempfile.mkstemp(dir=self._basetempdir) + os.close(fd) + fp = open(fn, 'w+') + self.addCleanup(fp.close) + self.addCleanup(_delete_file, fn) + return fp + + def mkdtemp(self): + """Create a temporary directory and return its path.""" + d = tempfile.mkdtemp(dir=self._basetempdir) + return d + + def write_file(self, path, content='xxx'): + """Write a file at the given path. + + path can be a string, a tuple or a list; if it's a tuple or list, + os.path.join will be used to produce a path. + """ + if isinstance(path, (list, tuple)): + path = os.path.join(*path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + + def create_dist(self, **kw): + """Create a stub distribution object and files. + + This function creates a Distribution instance (use keyword arguments + to customize it) and a temporary directory with a project structure + (currently an empty directory). + + It returns the path to the directory and the Distribution instance. + You can use self.write_file to write any file in that + directory, e.g. setup scripts or Python modules. + """ + if 'name' not in kw: + kw['name'] = 'foo' + tmp_dir = self.mkdtemp() + project_dir = os.path.join(tmp_dir, kw['name']) + os.mkdir(project_dir) + dist = Distribution(attrs=kw) + return project_dir, dist + + def assertIsFile(self, *args): + path = os.path.join(*args) + dirname = os.path.dirname(path) + file = os.path.basename(path) + if os.path.isdir(dirname): + files = os.listdir(dirname) + msg = "%s not found in %s: %s" % (file, dirname, files) + assert os.path.isfile(path), msg + else: + raise AssertionError( + '%s not found. %s does not exist' % (file, dirname)) + + def assertIsNotFile(self, *args): + path = os.path.join(*args) + self.assertFalse(os.path.isfile(path), "%r exists" % path) + + +class EnvironRestorer: + """TestCase-compatible mixin to restore or delete environment variables. + + The variables to restore (or delete if they were not originally present) + must be explicitly listed in self.restore_environ. It's better to be + aware of what we're modifying instead of saving and restoring the whole + environment. + """ + + def setUp(self): + super(EnvironRestorer, self).setUp() + self._saved = [] + self._added = [] + for key in self.restore_environ: + if key in os.environ: + self._saved.append((key, os.environ[key])) + else: + self._added.append(key) + + def tearDown(self): + for key, value in self._saved: + os.environ[key] = value + for key in self._added: + os.environ.pop(key, None) + super(EnvironRestorer, self).tearDown() + + +class DummyCommand: + """Class to store options for retrieval via set_undefined_options(). + + Useful for mocking one dependency command in the tests for another + command, see e.g. the dummy build command in test_build_scripts. + """ + + def __init__(self, **kwargs): + for kw, val in kwargs.items(): + setattr(self, kw, val) + + def ensure_finalized(self): + pass + + +class TestDistribution(Distribution): + """Distribution subclasses that avoids the default search for + configuration files. + + The ._config_files attribute must be set before + .parse_config_files() is called. + """ + + def find_config_files(self): + return self._config_files + + +def create_distribution(configfiles=()): + """Prepares a distribution with given config files parsed.""" + d = TestDistribution() + d.config.find_config_files = d.find_config_files + d._config_files = configfiles + d.parse_config_files() + d.parse_command_line() + return d + + +try: + from test.support import skip_unless_symlink +except ImportError: + skip_unless_symlink = unittest.skip( + 'requires test.support.skip_unless_symlink') diff --git a/Lib/packaging/tests/test_ccompiler.py b/Lib/packaging/tests/test_ccompiler.py new file mode 100644 index 0000000000..dd4bdd9d95 --- /dev/null +++ b/Lib/packaging/tests/test_ccompiler.py @@ -0,0 +1,15 @@ +"""Tests for distutils.compiler.ccompiler.""" + +from packaging.compiler import ccompiler +from packaging.tests import unittest, support + + +class CCompilerTestCase(unittest.TestCase): + pass # XXX need some tests on CCompiler + + +def test_suite(): + return unittest.makeSuite(CCompilerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_bdist.py b/Lib/packaging/tests/test_command_bdist.py new file mode 100644 index 0000000000..1522b7e214 --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist.py @@ -0,0 +1,77 @@ +"""Tests for distutils.command.bdist.""" + +from packaging import util +from packaging.command.bdist import bdist, show_formats + +from packaging.tests import unittest, support, captured_stdout + + +class BuildTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def _mock_get_platform(self): + self._get_platform_called = True + return self._get_platform() + + def setUp(self): + super(BuildTestCase, self).setUp() + + # mock util.get_platform + self._get_platform_called = False + self._get_platform = util.get_platform + util.get_platform = self._mock_get_platform + + def tearDown(self): + super(BuildTestCase, self).tearDown() + util.get_platform = self._get_platform + + def test_formats(self): + + # let's create a command and make sure + # we can fix the format + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.formats = ['msi'] + cmd.ensure_finalized() + self.assertEqual(cmd.formats, ['msi']) + + # what format bdist offers ? + # XXX an explicit list in bdist is + # not the best way to bdist_* commands + # we should add a registry + formats = sorted(('zip', 'gztar', 'bztar', 'ztar', + 'tar', 'wininst', 'msi')) + found = sorted(cmd.format_command) + self.assertEqual(found, formats) + + def test_skip_build(self): + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.skip_build = False + cmd.formats = ['ztar'] + cmd.ensure_finalized() + self.assertFalse(self._get_platform_called) + + pkg_pth, dist = self.create_dist() + cmd = bdist(dist) + cmd.skip_build = True + cmd.formats = ['ztar'] + cmd.ensure_finalized() + self.assertTrue(self._get_platform_called) + + def test_show_formats(self): + __, stdout = captured_stdout(show_formats) + + # the output should be a header line + one line per format + num_formats = len(bdist.format_commands) + output = [line for line in stdout.split('\n') + if line.strip().startswith('--formats=')] + self.assertEqual(len(output), num_formats) + + +def test_suite(): + return unittest.makeSuite(BuildTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_dumb.py b/Lib/packaging/tests/test_command_bdist_dumb.py new file mode 100644 index 0000000000..ce1563ff77 --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_dumb.py @@ -0,0 +1,103 @@ +"""Tests for distutils.command.bdist_dumb.""" + +import sys +import os + +# zlib is not used here, but if it's not available +# test_simple_built will fail +try: + import zlib +except ImportError: + zlib = None + +from packaging.dist import Distribution +from packaging.command.bdist_dumb import bdist_dumb +from packaging.tests import unittest, support + + +SETUP_PY = """\ +from distutils.run import setup +import foo + +setup(name='foo', version='0.1', py_modules=['foo'], + url='xxx', author='xxx', author_email='xxx') +""" + + +class BuildDumbTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(BuildDumbTestCase, self).setUp() + self.old_location = os.getcwd() + self.old_sys_argv = sys.argv, sys.argv[:] + + def tearDown(self): + os.chdir(self.old_location) + sys.argv = self.old_sys_argv[0] + sys.argv[:] = self.old_sys_argv[1] + super(BuildDumbTestCase, self).tearDown() + + @unittest.skipUnless(zlib, "requires zlib") + def test_simple_built(self): + + # let's create a simple package + tmp_dir = self.mkdtemp() + pkg_dir = os.path.join(tmp_dir, 'foo') + os.mkdir(pkg_dir) + self.write_file((pkg_dir, 'setup.py'), SETUP_PY) + self.write_file((pkg_dir, 'foo.py'), '#') + self.write_file((pkg_dir, 'MANIFEST.in'), 'include foo.py') + self.write_file((pkg_dir, 'README'), '') + + dist = Distribution({'name': 'foo', 'version': '0.1', + 'py_modules': ['foo'], + 'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx'}) + dist.script_name = 'setup.py' + os.chdir(pkg_dir) + + sys.argv[:] = ['setup.py'] + cmd = bdist_dumb(dist) + + # so the output is the same no matter + # what is the platform + cmd.format = 'zip' + + cmd.ensure_finalized() + cmd.run() + + # see what we have + dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) + base = "%s.%s" % (dist.get_fullname(), cmd.plat_name) + if os.name == 'os2': + base = base.replace(':', '-') + + wanted = ['%s.zip' % base] + self.assertEqual(dist_created, wanted) + + # now let's check what we have in the zip file + # XXX to be done + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + os.chdir(pkg_dir) + cmd = bdist_dumb(dist) + self.assertEqual(cmd.bdist_dir, None) + cmd.finalize_options() + + # bdist_dir is initialized to bdist_base/dumb if not set + base = cmd.get_finalized_command('bdist').bdist_base + self.assertEqual(cmd.bdist_dir, os.path.join(base, 'dumb')) + + # the format is set to a default value depending on the os.name + default = cmd.default_format[os.name] + self.assertEqual(cmd.format, default) + + +def test_suite(): + return unittest.makeSuite(BuildDumbTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_msi.py b/Lib/packaging/tests/test_command_bdist_msi.py new file mode 100644 index 0000000000..fded962dbf --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_msi.py @@ -0,0 +1,25 @@ +"""Tests for distutils.command.bdist_msi.""" +import sys + +from packaging.tests import unittest, support + + +class BDistMSITestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + @unittest.skipUnless(sys.platform == "win32", "runs only on win32") + def test_minimal(self): + # minimal test XXX need more tests + from packaging.command.bdist_msi import bdist_msi + pkg_pth, dist = self.create_dist() + cmd = bdist_msi(dist) + cmd.ensure_finalized() + + +def test_suite(): + return unittest.makeSuite(BDistMSITestCase) + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_bdist_wininst.py b/Lib/packaging/tests/test_command_bdist_wininst.py new file mode 100644 index 0000000000..09bdaadfc9 --- /dev/null +++ b/Lib/packaging/tests/test_command_bdist_wininst.py @@ -0,0 +1,32 @@ +"""Tests for distutils.command.bdist_wininst.""" + +from packaging.command.bdist_wininst import bdist_wininst +from packaging.tests import unittest, support + + +class BuildWinInstTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_get_exe_bytes(self): + + # issue5731: command was broken on non-windows platforms + # this test makes sure it works now for every platform + # let's create a command + pkg_pth, dist = self.create_dist() + cmd = bdist_wininst(dist) + cmd.ensure_finalized() + + # let's run the code that finds the right wininst*.exe file + # and make sure it finds it and returns its content + # no matter what platform we have + exe_file = cmd.get_exe_bytes() + self.assertGreater(len(exe_file), 10) + + +def test_suite(): + return unittest.makeSuite(BuildWinInstTestCase) + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_build.py b/Lib/packaging/tests/test_command_build.py new file mode 100644 index 0000000000..91fbe42a0d --- /dev/null +++ b/Lib/packaging/tests/test_command_build.py @@ -0,0 +1,55 @@ +"""Tests for distutils.command.build.""" +import os +import sys + +from packaging.command.build import build +from sysconfig import get_platform +from packaging.tests import unittest, support + + +class BuildTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = build(dist) + cmd.finalize_options() + + # if not specified, plat_name gets the current platform + self.assertEqual(cmd.plat_name, get_platform()) + + # build_purelib is build + lib + wanted = os.path.join(cmd.build_base, 'lib') + self.assertEqual(cmd.build_purelib, wanted) + + # build_platlib is 'build/lib.platform-x.x[-pydebug]' + # examples: + # build/lib.macosx-10.3-i386-2.7 + plat_spec = '.%s-%s' % (cmd.plat_name, sys.version[0:3]) + if hasattr(sys, 'gettotalrefcount'): + self.assertTrue(cmd.build_platlib.endswith('-pydebug')) + plat_spec += '-pydebug' + wanted = os.path.join(cmd.build_base, 'lib' + plat_spec) + self.assertEqual(cmd.build_platlib, wanted) + + # by default, build_lib = build_purelib + self.assertEqual(cmd.build_lib, cmd.build_purelib) + + # build_temp is build/temp. + wanted = os.path.join(cmd.build_base, 'temp' + plat_spec) + self.assertEqual(cmd.build_temp, wanted) + + # build_scripts is build/scripts-x.x + wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3]) + self.assertEqual(cmd.build_scripts, wanted) + + # executable is os.path.normpath(sys.executable) + self.assertEqual(cmd.executable, os.path.normpath(sys.executable)) + + +def test_suite(): + return unittest.makeSuite(BuildTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_clib.py b/Lib/packaging/tests/test_command_build_clib.py new file mode 100644 index 0000000000..a2a8583b0f --- /dev/null +++ b/Lib/packaging/tests/test_command_build_clib.py @@ -0,0 +1,141 @@ +"""Tests for distutils.command.build_clib.""" +import os +import sys + +from packaging.util import find_executable +from packaging.command.build_clib import build_clib +from packaging.errors import PackagingSetupError +from packaging.tests import unittest, support + + +class BuildCLibTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_check_library_dist(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + # 'libraries' option must be a list + self.assertRaises(PackagingSetupError, cmd.check_library_list, 'foo') + + # each element of 'libraries' must a 2-tuple + self.assertRaises(PackagingSetupError, cmd.check_library_list, + ['foo1', 'foo2']) + + # first element of each tuple in 'libraries' + # must be a string (the library name) + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [(1, 'foo1'), ('name', 'foo2')]) + + # library name may not contain directory separators + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [('name', 'foo1'), + ('another/name', 'foo2')]) + + # second element of each tuple must be a dictionary (build info) + self.assertRaises(PackagingSetupError, cmd.check_library_list, + [('name', {}), + ('another', 'foo2')]) + + # those work + libs = [('name', {}), ('name', {'ok': 'good'})] + cmd.check_library_list(libs) + + def test_get_source_files(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + # "in 'libraries' option 'sources' must be present and must be + # a list of source filenames + cmd.libraries = [('name', {})] + self.assertRaises(PackagingSetupError, cmd.get_source_files) + + cmd.libraries = [('name', {'sources': 1})] + self.assertRaises(PackagingSetupError, cmd.get_source_files) + + cmd.libraries = [('name', {'sources': ['a', 'b']})] + self.assertEqual(cmd.get_source_files(), ['a', 'b']) + + cmd.libraries = [('name', {'sources': ('a', 'b')})] + self.assertEqual(cmd.get_source_files(), ['a', 'b']) + + cmd.libraries = [('name', {'sources': ('a', 'b')}), + ('name2', {'sources': ['c', 'd']})] + self.assertEqual(cmd.get_source_files(), ['a', 'b', 'c', 'd']) + + def test_build_libraries(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + class FakeCompiler: + def compile(*args, **kw): + pass + create_static_lib = compile + + cmd.compiler = FakeCompiler() + + # build_libraries is also doing a bit of type checking + lib = [('name', {'sources': 'notvalid'})] + self.assertRaises(PackagingSetupError, cmd.build_libraries, lib) + + lib = [('name', {'sources': []})] + cmd.build_libraries(lib) + + lib = [('name', {'sources': ()})] + cmd.build_libraries(lib) + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + cmd.include_dirs = 'one-dir' + cmd.finalize_options() + self.assertEqual(cmd.include_dirs, ['one-dir']) + + cmd.include_dirs = None + cmd.finalize_options() + self.assertEqual(cmd.include_dirs, []) + + cmd.distribution.libraries = 'WONTWORK' + self.assertRaises(PackagingSetupError, cmd.finalize_options) + + @unittest.skipIf(sys.platform == 'win32', 'disabled on win32') + def test_run(self): + pkg_dir, dist = self.create_dist() + cmd = build_clib(dist) + + foo_c = os.path.join(pkg_dir, 'foo.c') + self.write_file(foo_c, 'int main(void) { return 1;}\n') + cmd.libraries = [('foo', {'sources': [foo_c]})] + + build_temp = os.path.join(pkg_dir, 'build') + os.mkdir(build_temp) + cmd.build_temp = build_temp + cmd.build_clib = build_temp + + # before we run the command, we want to make sure + # all commands are present on the system + # by creating a compiler and checking its executables + from packaging.compiler import new_compiler, customize_compiler + + compiler = new_compiler() + customize_compiler(compiler) + for ccmd in compiler.executables.values(): + if ccmd is None: + continue + if find_executable(ccmd[0]) is None: + raise unittest.SkipTest("can't test") + + # this should work + cmd.run() + + # let's check the result + self.assertIn('libfoo.a', os.listdir(build_temp)) + + +def test_suite(): + return unittest.makeSuite(BuildCLibTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_ext.py b/Lib/packaging/tests/test_command_build_ext.py new file mode 100644 index 0000000000..2d798422b2 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_ext.py @@ -0,0 +1,353 @@ +import os +import sys +import site +import shutil +import sysconfig +from io import StringIO +from packaging.dist import Distribution +from packaging.errors import UnknownFileError, CompileError +from packaging.command.build_ext import build_ext +from packaging.compiler.extension import Extension + +from packaging.tests import support, unittest, verbose, unload + +# http://bugs.python.org/issue4373 +# Don't load the xx module more than once. +ALREADY_TESTED = False + + +def _get_source_filename(): + srcdir = sysconfig.get_config_var('srcdir') + return os.path.join(srcdir, 'Modules', 'xxmodule.c') + + +class BuildExtTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + def setUp(self): + # Create a simple test environment + # Note that we're making changes to sys.path + super(BuildExtTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.sys_path = sys.path, sys.path[:] + sys.path.append(self.tmp_dir) + shutil.copy(_get_source_filename(), self.tmp_dir) + self.old_user_base = site.USER_BASE + site.USER_BASE = self.mkdtemp() + build_ext.USER_BASE = site.USER_BASE + + def test_build_ext(self): + global ALREADY_TESTED + xx_c = os.path.join(self.tmp_dir, 'xxmodule.c') + xx_ext = Extension('xx', [xx_c]) + dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]}) + dist.package_dir = self.tmp_dir + cmd = build_ext(dist) + if os.name == "nt": + # On Windows, we must build a debug version iff running + # a debug build of Python + cmd.debug = sys.executable.endswith("_d.exe") + cmd.build_lib = self.tmp_dir + cmd.build_temp = self.tmp_dir + + old_stdout = sys.stdout + if not verbose: + # silence compiler output + sys.stdout = StringIO() + try: + cmd.ensure_finalized() + cmd.run() + finally: + sys.stdout = old_stdout + + if ALREADY_TESTED: + return + else: + ALREADY_TESTED = True + + import xx + + for attr in ('error', 'foo', 'new', 'roj'): + self.assertTrue(hasattr(xx, attr)) + + self.assertEqual(xx.foo(2, 5), 7) + self.assertEqual(xx.foo(13, 15), 28) + self.assertEqual(xx.new().demo(), None) + doc = 'This is a template module just for instruction.' + self.assertEqual(xx.__doc__, doc) + self.assertTrue(isinstance(xx.Null(), xx.Null)) + self.assertTrue(isinstance(xx.Str(), xx.Str)) + + def tearDown(self): + # Get everything back to normal + unload('xx') + sys.path = self.sys_path[0] + sys.path[:] = self.sys_path[1] + if sys.version > "2.6": + site.USER_BASE = self.old_user_base + build_ext.USER_BASE = self.old_user_base + + super(BuildExtTestCase, self).tearDown() + + def test_solaris_enable_shared(self): + dist = Distribution({'name': 'xx'}) + cmd = build_ext(dist) + old = sys.platform + + sys.platform = 'sunos' # fooling finalize_options + from sysconfig import _CONFIG_VARS + + old_var = _CONFIG_VARS.get('Py_ENABLE_SHARED') + _CONFIG_VARS['Py_ENABLE_SHARED'] = 1 + try: + cmd.ensure_finalized() + finally: + sys.platform = old + if old_var is None: + del _CONFIG_VARS['Py_ENABLE_SHARED'] + else: + _CONFIG_VARS['Py_ENABLE_SHARED'] = old_var + + # make sure we get some library dirs under solaris + self.assertGreater(len(cmd.library_dirs), 0) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_user_site(self): + dist = Distribution({'name': 'xx'}) + cmd = build_ext(dist) + + # making sure the user option is there + options = [name for name, short, label in + cmd.user_options] + self.assertIn('user', options) + + # setting a value + cmd.user = True + + # setting user based lib and include + lib = os.path.join(site.USER_BASE, 'lib') + incl = os.path.join(site.USER_BASE, 'include') + os.mkdir(lib) + os.mkdir(incl) + + # let's run finalize + cmd.ensure_finalized() + + # see if include_dirs and library_dirs + # were set + self.assertIn(lib, cmd.library_dirs) + self.assertIn(lib, cmd.rpath) + self.assertIn(incl, cmd.include_dirs) + + def test_optional_extension(self): + + # this extension will fail, but let's ignore this failure + # with the optional argument. + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertRaises((UnknownFileError, CompileError), + cmd.run) # should raise an error + + modules = [Extension('foo', ['xxx'], optional=True)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + cmd.run() # should pass + + def test_finalize_options(self): + # Make sure Python's include directories (for Python.h, pyconfig.h, + # etc.) are in the include search path. + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.finalize_options() + + py_include = sysconfig.get_path('include') + self.assertIn(py_include, cmd.include_dirs) + + plat_py_include = sysconfig.get_path('platinclude') + self.assertIn(plat_py_include, cmd.include_dirs) + + # make sure cmd.libraries is turned into a list + # if it's a string + cmd = build_ext(dist) + cmd.libraries = 'my_lib' + cmd.finalize_options() + self.assertEqual(cmd.libraries, ['my_lib']) + + # make sure cmd.library_dirs is turned into a list + # if it's a string + cmd = build_ext(dist) + cmd.library_dirs = 'my_lib_dir' + cmd.finalize_options() + self.assertIn('my_lib_dir', cmd.library_dirs) + + # make sure rpath is turned into a list + # if it's a list of os.pathsep's paths + cmd = build_ext(dist) + cmd.rpath = os.pathsep.join(['one', 'two']) + cmd.finalize_options() + self.assertEqual(cmd.rpath, ['one', 'two']) + + # XXX more tests to perform for win32 + + # make sure define is turned into 2-tuples + # strings if they are ','-separated strings + cmd = build_ext(dist) + cmd.define = 'one,two' + cmd.finalize_options() + self.assertEqual(cmd.define, [('one', '1'), ('two', '1')]) + + # make sure undef is turned into a list of + # strings if they are ','-separated strings + cmd = build_ext(dist) + cmd.undef = 'one,two' + cmd.finalize_options() + self.assertEqual(cmd.undef, ['one', 'two']) + + # make sure swig_opts is turned into a list + cmd = build_ext(dist) + cmd.swig_opts = None + cmd.finalize_options() + self.assertEqual(cmd.swig_opts, []) + + cmd = build_ext(dist) + cmd.swig_opts = '1 2' + cmd.finalize_options() + self.assertEqual(cmd.swig_opts, ['1', '2']) + + def test_get_source_files(self): + modules = [Extension('foo', ['xxx'], optional=False)] + dist = Distribution({'name': 'xx', 'ext_modules': modules}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertEqual(cmd.get_source_files(), ['xxx']) + + def test_compiler_option(self): + # cmd.compiler is an option and + # should not be overriden by a compiler instance + # when the command is run + dist = Distribution() + cmd = build_ext(dist) + cmd.compiler = 'unix' + cmd.ensure_finalized() + cmd.run() + self.assertEqual(cmd.compiler, 'unix') + + def test_get_outputs(self): + tmp_dir = self.mkdtemp() + c_file = os.path.join(tmp_dir, 'foo.c') + self.write_file(c_file, 'void initfoo(void) {};\n') + ext = Extension('foo', [c_file], optional=False) + dist = Distribution({'name': 'xx', + 'ext_modules': [ext]}) + cmd = build_ext(dist) + cmd.ensure_finalized() + self.assertEqual(len(cmd.get_outputs()), 1) + + if os.name == "nt": + cmd.debug = sys.executable.endswith("_d.exe") + + cmd.build_lib = os.path.join(self.tmp_dir, 'build') + cmd.build_temp = os.path.join(self.tmp_dir, 'tempt') + + # issue #5977 : distutils build_ext.get_outputs + # returns wrong result with --inplace + other_tmp_dir = os.path.realpath(self.mkdtemp()) + old_wd = os.getcwd() + os.chdir(other_tmp_dir) + try: + cmd.inplace = True + cmd.run() + so_file = cmd.get_outputs()[0] + finally: + os.chdir(old_wd) + self.assertTrue(os.path.exists(so_file)) + so_ext = sysconfig.get_config_var('SO') + self.assertTrue(so_file.endswith(so_ext)) + so_dir = os.path.dirname(so_file) + self.assertEqual(so_dir, other_tmp_dir) + + cmd.inplace = False + cmd.run() + so_file = cmd.get_outputs()[0] + self.assertTrue(os.path.exists(so_file)) + self.assertTrue(so_file.endswith(so_ext)) + so_dir = os.path.dirname(so_file) + self.assertEqual(so_dir, cmd.build_lib) + + # inplace = False, cmd.package = 'bar' + build_py = cmd.get_finalized_command('build_py') + build_py.package_dir = 'bar' + path = cmd.get_ext_fullpath('foo') + # checking that the last directory is the build_dir + path = os.path.split(path)[0] + self.assertEqual(path, cmd.build_lib) + + # inplace = True, cmd.package = 'bar' + cmd.inplace = True + other_tmp_dir = os.path.realpath(self.mkdtemp()) + old_wd = os.getcwd() + os.chdir(other_tmp_dir) + try: + path = cmd.get_ext_fullpath('foo') + finally: + os.chdir(old_wd) + # checking that the last directory is bar + path = os.path.split(path)[0] + lastdir = os.path.split(path)[-1] + self.assertEqual(lastdir, 'bar') + + def test_ext_fullpath(self): + ext = sysconfig.get_config_vars()['SO'] + # building lxml.etree inplace + #etree_c = os.path.join(self.tmp_dir, 'lxml.etree.c') + #etree_ext = Extension('lxml.etree', [etree_c]) + #dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]}) + dist = Distribution() + cmd = build_ext(dist) + cmd.inplace = True + cmd.distribution.package_dir = 'src' + cmd.distribution.packages = ['lxml', 'lxml.html'] + curdir = os.getcwd() + wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) + path = cmd.get_ext_fullpath('lxml.etree') + self.assertEqual(wanted, path) + + # building lxml.etree not inplace + cmd.inplace = False + cmd.build_lib = os.path.join(curdir, 'tmpdir') + wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext) + path = cmd.get_ext_fullpath('lxml.etree') + self.assertEqual(wanted, path) + + # building twisted.runner.portmap not inplace + build_py = cmd.get_finalized_command('build_py') + build_py.package_dir = None + cmd.distribution.packages = ['twisted', 'twisted.runner.portmap'] + path = cmd.get_ext_fullpath('twisted.runner.portmap') + wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner', + 'portmap' + ext) + self.assertEqual(wanted, path) + + # building twisted.runner.portmap inplace + cmd.inplace = True + path = cmd.get_ext_fullpath('twisted.runner.portmap') + wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext) + self.assertEqual(wanted, path) + + +def test_suite(): + src = _get_source_filename() + if not os.path.exists(src): + if verbose: + print ('test_build_ext: Cannot find source code (test' + ' must run in python build dir)') + return unittest.TestSuite() + else: + return unittest.makeSuite(BuildExtTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_build_py.py b/Lib/packaging/tests/test_command_build_py.py new file mode 100644 index 0000000000..9b40e6d9b2 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_py.py @@ -0,0 +1,124 @@ +"""Tests for distutils.command.build_py.""" + +import os +import sys + +from packaging.command.build_py import build_py +from packaging.dist import Distribution +from packaging.errors import PackagingFileError + +from packaging.tests import unittest, support + + +class BuildPyTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_package_data(self): + sources = self.mkdtemp() + pkg_dir = os.path.join(sources, 'pkg') + os.mkdir(pkg_dir) + f = open(os.path.join(pkg_dir, "__init__.py"), "w") + try: + f.write("# Pretend this is a package.") + finally: + f.close() + f = open(os.path.join(pkg_dir, "README.txt"), "w") + try: + f.write("Info about this package") + finally: + f.close() + + destination = self.mkdtemp() + + dist = Distribution({"packages": ["pkg"], + "package_dir": sources}) + # script_name need not exist, it just need to be initialized + + dist.script_name = os.path.join(sources, "setup.py") + dist.command_obj["build"] = support.DummyCommand( + force=False, + build_lib=destination, + use_2to3_fixers=None, + convert_2to3_doctests=None, + use_2to3=False) + dist.packages = ["pkg"] + dist.package_data = {"pkg": ["README.txt"]} + dist.package_dir = sources + + cmd = build_py(dist) + cmd.compile = True + cmd.ensure_finalized() + self.assertEqual(cmd.package_data, dist.package_data) + + cmd.run() + + # This makes sure the list of outputs includes byte-compiled + # files for Python modules but not for package data files + # (there shouldn't *be* byte-code files for those!). + # + self.assertEqual(len(cmd.get_outputs()), 3) + pkgdest = os.path.join(destination, "pkg") + files = os.listdir(pkgdest) + self.assertIn("__init__.py", files) + self.assertIn("__init__.pyc", files) + self.assertIn("README.txt", files) + + def test_empty_package_dir(self): + # See SF 1668596/1720897. + cwd = os.getcwd() + + # create the distribution files. + sources = self.mkdtemp() + pkg = os.path.join(sources, 'pkg') + os.mkdir(pkg) + open(os.path.join(pkg, "__init__.py"), "w").close() + testdir = os.path.join(pkg, "doc") + os.mkdir(testdir) + open(os.path.join(testdir, "testfile"), "w").close() + + os.chdir(sources) + old_stdout = sys.stdout + #sys.stdout = StringIO.StringIO() + + try: + dist = Distribution({"packages": ["pkg"], + "package_dir": sources, + "package_data": {"pkg": ["doc/*"]}}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(sources, "setup.py") + dist.script_args = ["build"] + dist.parse_command_line() + + try: + dist.run_commands() + except PackagingFileError as e: + self.fail("failed package_data test when package_dir is ''") + finally: + # Restore state. + os.chdir(cwd) + sys.stdout = old_stdout + + @unittest.skipUnless(hasattr(sys, 'dont_write_bytecode'), + 'sys.dont_write_bytecode not supported') + def test_dont_write_bytecode(self): + # makes sure byte_compile is not used + pkg_dir, dist = self.create_dist() + cmd = build_py(dist) + cmd.compile = True + cmd.optimize = 1 + + old_dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + cmd.byte_compile([]) + finally: + sys.dont_write_bytecode = old_dont_write_bytecode + + self.assertIn('byte-compiling is disabled', self.get_logs()[0]) + +def test_suite(): + return unittest.makeSuite(BuildPyTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_build_scripts.py b/Lib/packaging/tests/test_command_build_scripts.py new file mode 100644 index 0000000000..60d8b68d80 --- /dev/null +++ b/Lib/packaging/tests/test_command_build_scripts.py @@ -0,0 +1,112 @@ +"""Tests for distutils.command.build_scripts.""" + +import os +import sys +import sysconfig +from packaging.dist import Distribution +from packaging.command.build_scripts import build_scripts + +from packaging.tests import unittest, support + + +class BuildScriptsTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_default_settings(self): + cmd = self.get_build_scripts_cmd("/foo/bar", []) + self.assertFalse(cmd.force) + self.assertIs(cmd.build_dir, None) + + cmd.finalize_options() + + self.assertTrue(cmd.force) + self.assertEqual(cmd.build_dir, "/foo/bar") + + def test_build(self): + source = self.mkdtemp() + target = self.mkdtemp() + expected = self.write_sample_scripts(source) + + cmd = self.get_build_scripts_cmd(target, + [os.path.join(source, fn) + for fn in expected]) + cmd.finalize_options() + cmd.run() + + built = os.listdir(target) + for name in expected: + self.assertIn(name, built) + + def get_build_scripts_cmd(self, target, scripts): + dist = Distribution() + dist.scripts = scripts + dist.command_obj["build"] = support.DummyCommand( + build_scripts=target, + force=True, + executable=sys.executable, + use_2to3=False, + use_2to3_fixers=None, + convert_2to3_doctests=None + ) + return build_scripts(dist) + + def write_sample_scripts(self, dir): + expected = [] + expected.append("script1.py") + self.write_script(dir, "script1.py", + ("#! /usr/bin/env python2.3\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + expected.append("script2.py") + self.write_script(dir, "script2.py", + ("#!/usr/bin/python\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + expected.append("shell.sh") + self.write_script(dir, "shell.sh", + ("#!/bin/sh\n" + "# bogus shell script w/ sh-bang\n" + "exit 0\n")) + return expected + + def write_script(self, dir, name, text): + f = open(os.path.join(dir, name), "w") + try: + f.write(text) + finally: + f.close() + + def test_version_int(self): + source = self.mkdtemp() + target = self.mkdtemp() + expected = self.write_sample_scripts(source) + + + cmd = self.get_build_scripts_cmd(target, + [os.path.join(source, fn) + for fn in expected]) + cmd.finalize_options() + + # http://bugs.python.org/issue4524 + # + # On linux-g++-32 with command line `./configure --enable-ipv6 + # --with-suffix=3`, python is compiled okay but the build scripts + # failed when writing the name of the executable + old = sysconfig.get_config_vars().get('VERSION') + sysconfig._CONFIG_VARS['VERSION'] = 4 + try: + cmd.run() + finally: + if old is not None: + sysconfig._CONFIG_VARS['VERSION'] = old + + built = os.listdir(target) + for name in expected: + self.assertIn(name, built) + +def test_suite(): + return unittest.makeSuite(BuildScriptsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_check.py b/Lib/packaging/tests/test_command_check.py new file mode 100644 index 0000000000..8b32673c1e --- /dev/null +++ b/Lib/packaging/tests/test_command_check.py @@ -0,0 +1,131 @@ +"""Tests for distutils.command.check.""" + +import logging +from packaging.command.check import check +from packaging.metadata import _HAS_DOCUTILS +from packaging.errors import PackagingSetupError, MetadataMissingError +from packaging.tests import unittest, support + + +class CheckTestCase(support.LoggingCatcher, + support.TempdirManager, + unittest.TestCase): + + def _run(self, metadata=None, **options): + if metadata is None: + metadata = {'name': 'xxx', 'version': '1.2'} + pkg_info, dist = self.create_dist(**metadata) + cmd = check(dist) + cmd.initialize_options() + for name, value in options.items(): + setattr(cmd, name, value) + cmd.ensure_finalized() + cmd.run() + return cmd + + def test_check_metadata(self): + # let's run the command with no metadata at all + # by default, check is checking the metadata + # should have some warnings + cmd = self._run() + # trick: using assertNotEqual with an empty list will give us a more + # useful error message than assertGreater(.., 0) when the code change + # and the test fails + self.assertNotEqual([], self.get_logs(logging.WARNING)) + + # now let's add the required fields + # and run it again, to make sure we don't get + # any warning anymore + self.loghandler.flush() + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': '4.2', + } + cmd = self._run(metadata) + self.assertEqual([], self.get_logs(logging.WARNING)) + + # now with the strict mode, we should + # get an error if there are missing metadata + self.assertRaises(MetadataMissingError, self._run, {}, **{'strict': 1}) + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1}) + + # and of course, no error when all metadata fields are present + self.loghandler.flush() + cmd = self._run(metadata, strict=True) + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_check_metadata_1_2(self): + # let's run the command with no metadata at all + # by default, check is checking the metadata + # should have some warnings + cmd = self._run() + self.assertNotEqual([], self.get_logs(logging.WARNING)) + + # now let's add the required fields and run it again, to make sure we + # don't get any warning anymore let's use requires_python as a marker + # to enforce Metadata-Version 1.2 + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': '4.2', + 'requires_python': '2.4', + } + self.loghandler.flush() + cmd = self._run(metadata) + self.assertEqual([], self.get_logs(logging.WARNING)) + + # now with the strict mode, we should + # get an error if there are missing metadata + self.assertRaises(MetadataMissingError, self._run, {}, **{'strict': 1}) + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1}) + + # complain about version format + metadata['version'] = 'xxx' + self.assertRaises(PackagingSetupError, self._run, metadata, + **{'strict': 1}) + + # now with correct version format again + metadata['version'] = '4.2' + self.loghandler.flush() + cmd = self._run(metadata, strict=True) + self.assertEqual([], self.get_logs(logging.WARNING)) + + @unittest.skipUnless(_HAS_DOCUTILS, "requires docutils") + def test_check_restructuredtext(self): + # let's see if it detects broken rest in long_description + broken_rest = 'title\n===\n\ntest' + pkg_info, dist = self.create_dist(description=broken_rest) + cmd = check(dist) + cmd.check_restructuredtext() + self.assertEqual(len(self.get_logs(logging.WARNING)), 1) + + self.loghandler.flush() + pkg_info, dist = self.create_dist(description='title\n=====\n\ntest') + cmd = check(dist) + cmd.check_restructuredtext() + self.assertEqual([], self.get_logs(logging.WARNING)) + + def test_check_all(self): + self.assertRaises(PackagingSetupError, self._run, + {'name': 'xxx', 'version': 'xxx'}, **{'strict': 1, + 'all': 1}) + self.assertRaises(MetadataMissingError, self._run, + {}, **{'strict': 1, + 'all': 1}) + + def test_check_hooks(self): + pkg_info, dist = self.create_dist() + dist.command_options['install_dist'] = { + 'pre_hook': ('file', {"a": 'some.nonextistant.hook.ghrrraarrhll'}), + } + cmd = check(dist) + cmd.check_hooks_resolvable() + self.assertEqual(len(self.get_logs(logging.WARNING)), 1) + + +def test_suite(): + return unittest.makeSuite(CheckTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_clean.py b/Lib/packaging/tests/test_command_clean.py new file mode 100644 index 0000000000..8d29e4dcdc --- /dev/null +++ b/Lib/packaging/tests/test_command_clean.py @@ -0,0 +1,48 @@ +"""Tests for distutils.command.clean.""" +import os + +from packaging.command.clean import clean +from packaging.tests import unittest, support + + +class cleanTestCase(support.TempdirManager, support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + pkg_dir, dist = self.create_dist() + cmd = clean(dist) + + # let's add some elements clean should remove + dirs = [(d, os.path.join(pkg_dir, d)) + for d in ('build_temp', 'build_lib', 'bdist_base', + 'build_scripts', 'build_base')] + + for name, path in dirs: + os.mkdir(path) + setattr(cmd, name, path) + if name == 'build_base': + continue + for f in ('one', 'two', 'three'): + self.write_file(os.path.join(path, f)) + + # let's run the command + cmd.all = True + cmd.ensure_finalized() + cmd.run() + + # make sure the files where removed + for name, path in dirs: + self.assertFalse(os.path.exists(path), + '%r was not removed' % path) + + # let's run the command again (should spit warnings but succeed) + cmd.all = True + cmd.ensure_finalized() + cmd.run() + + +def test_suite(): + return unittest.makeSuite(cleanTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_cmd.py b/Lib/packaging/tests/test_command_cmd.py new file mode 100644 index 0000000000..8ac9dce66d --- /dev/null +++ b/Lib/packaging/tests/test_command_cmd.py @@ -0,0 +1,101 @@ +"""Tests for distutils.cmd.""" +import os + +from packaging.command.cmd import Command +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError +from packaging.tests import support, unittest + + +class MyCmd(Command): + def initialize_options(self): + pass + + +class CommandTestCase(support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(CommandTestCase, self).setUp() + dist = Distribution() + self.cmd = MyCmd(dist) + + def test_make_file(self): + cmd = self.cmd + + # making sure it raises when infiles is not a string or a list/tuple + self.assertRaises(TypeError, cmd.make_file, + infiles=1, outfile='', func='func', args=()) + + # making sure execute gets called properly + def _execute(func, args, exec_msg, level): + self.assertEqual(exec_msg, 'generating out from in') + cmd.force = True + cmd.execute = _execute + cmd.make_file(infiles='in', outfile='out', func='func', args=()) + + def test_dump_options(self): + cmd = self.cmd + cmd.option1 = 1 + cmd.option2 = 1 + cmd.user_options = [('option1', '', ''), ('option2', '', '')] + cmd.dump_options() + + wanted = ["command options for 'MyCmd':", ' option1 = 1', + ' option2 = 1'] + msgs = self.get_logs() + self.assertEqual(msgs, wanted) + + def test_ensure_string(self): + cmd = self.cmd + cmd.option1 = 'ok' + cmd.ensure_string('option1') + + cmd.option2 = None + cmd.ensure_string('option2', 'xxx') + self.assertTrue(hasattr(cmd, 'option2')) + + cmd.option3 = 1 + self.assertRaises(PackagingOptionError, cmd.ensure_string, 'option3') + + def test_ensure_string_list(self): + cmd = self.cmd + cmd.option1 = 'ok,dok' + cmd.ensure_string_list('option1') + self.assertEqual(cmd.option1, ['ok', 'dok']) + + cmd.yes_string_list = ['one', 'two', 'three'] + cmd.yes_string_list2 = 'ok' + cmd.ensure_string_list('yes_string_list') + cmd.ensure_string_list('yes_string_list2') + self.assertEqual(cmd.yes_string_list, ['one', 'two', 'three']) + self.assertEqual(cmd.yes_string_list2, ['ok']) + + cmd.not_string_list = ['one', 2, 'three'] + cmd.not_string_list2 = object() + self.assertRaises(PackagingOptionError, + cmd.ensure_string_list, 'not_string_list') + + self.assertRaises(PackagingOptionError, + cmd.ensure_string_list, 'not_string_list2') + + def test_ensure_filename(self): + cmd = self.cmd + cmd.option1 = __file__ + cmd.ensure_filename('option1') + cmd.option2 = 'xxx' + self.assertRaises(PackagingOptionError, cmd.ensure_filename, 'option2') + + def test_ensure_dirname(self): + cmd = self.cmd + cmd.option1 = os.path.dirname(__file__) or os.curdir + cmd.ensure_dirname('option1') + cmd.option2 = 'xxx' + self.assertRaises(PackagingOptionError, cmd.ensure_dirname, 'option2') + + +def test_suite(): + return unittest.makeSuite(CommandTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_command_config.py b/Lib/packaging/tests/test_command_config.py new file mode 100644 index 0000000000..6d780c5fd5 --- /dev/null +++ b/Lib/packaging/tests/test_command_config.py @@ -0,0 +1,76 @@ +"""Tests for distutils.command.config.""" +import os +import sys +import logging + +from packaging.command.config import dump_file, config +from packaging.tests import unittest, support + + +class ConfigTestCase(support.LoggingCatcher, + support.TempdirManager, + unittest.TestCase): + + def test_dump_file(self): + this_file = __file__.rstrip('co') + with open(this_file) as f: + numlines = len(f.readlines()) + + dump_file(this_file, 'I am the header') + + logs = [] + for log in self.get_logs(logging.INFO): + logs.extend(line for line in log.split('\n')) + self.assertEqual(len(logs), numlines + 2) + + @unittest.skipIf(sys.platform == 'win32', 'disabled on win32') + def test_search_cpp(self): + pkg_dir, dist = self.create_dist() + cmd = config(dist) + + # simple pattern searches + match = cmd.search_cpp(pattern='xxx', body='// xxx') + self.assertEqual(match, 0) + + match = cmd.search_cpp(pattern='_configtest', body='// xxx') + self.assertEqual(match, 1) + + def test_finalize_options(self): + # finalize_options does a bit of transformation + # on options + pkg_dir, dist = self.create_dist() + cmd = config(dist) + cmd.include_dirs = 'one%stwo' % os.pathsep + cmd.libraries = 'one' + cmd.library_dirs = 'three%sfour' % os.pathsep + cmd.ensure_finalized() + + self.assertEqual(cmd.include_dirs, ['one', 'two']) + self.assertEqual(cmd.libraries, ['one']) + self.assertEqual(cmd.library_dirs, ['three', 'four']) + + def test_clean(self): + # _clean removes files + tmp_dir = self.mkdtemp() + f1 = os.path.join(tmp_dir, 'one') + f2 = os.path.join(tmp_dir, 'two') + + self.write_file(f1, 'xxx') + self.write_file(f2, 'xxx') + + for f in (f1, f2): + self.assertTrue(os.path.exists(f)) + + pkg_dir, dist = self.create_dist() + cmd = config(dist) + cmd._clean(f1, f2) + + for f in (f1, f2): + self.assertFalse(os.path.exists(f)) + + +def test_suite(): + return unittest.makeSuite(ConfigTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_data.py b/Lib/packaging/tests/test_command_install_data.py new file mode 100644 index 0000000000..8b8bbac774 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_data.py @@ -0,0 +1,80 @@ +"""Tests for packaging.command.install_data.""" +import os +import sysconfig +from sysconfig import _get_default_scheme +from packaging.tests import unittest, support +from packaging.command.install_data import install_data + + +class InstallDataTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + self.addCleanup(setattr, sysconfig, '_SCHEMES', sysconfig._SCHEMES) + + pkg_dir, dist = self.create_dist() + cmd = install_data(dist) + cmd.install_dir = inst = os.path.join(pkg_dir, 'inst') + + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst', + os.path.join(pkg_dir, 'inst')) + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst2', + os.path.join(pkg_dir, 'inst2')) + + one = os.path.join(pkg_dir, 'one') + self.write_file(one, 'xxx') + inst2 = os.path.join(pkg_dir, 'inst2') + two = os.path.join(pkg_dir, 'two') + self.write_file(two, 'xxx') + + cmd.data_files = {one: '{inst}/one', two: '{inst2}/two'} + self.assertCountEqual(cmd.get_inputs(), [one, two]) + + # let's run the command + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 2) + rtwo = os.path.split(two)[-1] + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + rone = os.path.split(one)[-1] + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + cmd.outfiles = [] + + # let's try with warn_dir one + cmd.warn_dir = True + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 2) + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + cmd.outfiles = [] + + # now using root and empty dir + cmd.root = os.path.join(pkg_dir, 'root') + three = os.path.join(cmd.install_dir, 'three') + self.write_file(three, 'xx') + + sysconfig._SCHEMES.set(_get_default_scheme(), 'inst3', + cmd.install_dir) + + cmd.data_files = {one: '{inst}/one', two: '{inst2}/two', + three: '{inst3}/three'} + cmd.ensure_finalized() + cmd.run() + + # let's check the result + self.assertEqual(len(cmd.get_outputs()), 3) + self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + self.assertTrue(os.path.exists(os.path.join(inst, rone))) + + +def test_suite(): + return unittest.makeSuite(InstallDataTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_dist.py b/Lib/packaging/tests/test_command_install_dist.py new file mode 100644 index 0000000000..a06d1f65a4 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_dist.py @@ -0,0 +1,210 @@ +"""Tests for packaging.command.install.""" + +import os +import sys + +from sysconfig import (get_scheme_names, get_config_vars, + _SCHEMES, get_config_var, get_path) + +_CONFIG_VARS = get_config_vars() + +from packaging.tests import captured_stdout + +from packaging.command.install_dist import install_dist +from packaging.command import install_dist as install_module +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError + +from packaging.tests import unittest, support + + +class InstallTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_home_installation_scheme(self): + # This ensure two things: + # - that --home generates the desired set of directory names + # - test --home is supported on all platforms + builddir = self.mkdtemp() + destination = os.path.join(builddir, "installation") + + dist = Distribution({"name": "foopkg"}) + # script_name need not exist, it just need to be initialized + dist.script_name = os.path.join(builddir, "setup.py") + dist.command_obj["build"] = support.DummyCommand( + build_base=builddir, + build_lib=os.path.join(builddir, "lib"), + ) + + old_posix_prefix = _SCHEMES.get('posix_prefix', 'platinclude') + old_posix_home = _SCHEMES.get('posix_home', 'platinclude') + + new_path = '{platbase}/include/python{py_version_short}' + _SCHEMES.set('posix_prefix', 'platinclude', new_path) + _SCHEMES.set('posix_home', 'platinclude', '{platbase}/include/python') + + try: + cmd = install_dist(dist) + cmd.home = destination + cmd.ensure_finalized() + finally: + _SCHEMES.set('posix_prefix', 'platinclude', old_posix_prefix) + _SCHEMES.set('posix_home', 'platinclude', old_posix_home) + + self.assertEqual(cmd.install_base, destination) + self.assertEqual(cmd.install_platbase, destination) + + def check_path(got, expected): + got = os.path.normpath(got) + expected = os.path.normpath(expected) + self.assertEqual(got, expected) + + libdir = os.path.join(destination, "lib", "python") + check_path(cmd.install_lib, libdir) + check_path(cmd.install_platlib, libdir) + check_path(cmd.install_purelib, libdir) + check_path(cmd.install_headers, + os.path.join(destination, "include", "python", "foopkg")) + check_path(cmd.install_scripts, os.path.join(destination, "bin")) + check_path(cmd.install_data, destination) + + @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher') + def test_user_site(self): + # test install with --user + # preparing the environment for the test + self.old_user_base = get_config_var('userbase') + self.old_user_site = get_path('purelib', '%s_user' % os.name) + self.tmpdir = self.mkdtemp() + self.user_base = os.path.join(self.tmpdir, 'B') + self.user_site = os.path.join(self.tmpdir, 'S') + _CONFIG_VARS['userbase'] = self.user_base + scheme = '%s_user' % os.name + _SCHEMES.set(scheme, 'purelib', self.user_site) + + def _expanduser(path): + if path[0] == '~': + path = os.path.normpath(self.tmpdir) + path[1:] + return path + + self.old_expand = os.path.expanduser + os.path.expanduser = _expanduser + + try: + # this is the actual test + self._test_user_site() + finally: + _CONFIG_VARS['userbase'] = self.old_user_base + _SCHEMES.set(scheme, 'purelib', self.old_user_site) + os.path.expanduser = self.old_expand + + def _test_user_site(self): + schemes = get_scheme_names() + for key in ('nt_user', 'posix_user', 'os2_home'): + self.assertIn(key, schemes) + + dist = Distribution({'name': 'xx'}) + cmd = install_dist(dist) + # making sure the user option is there + options = [name for name, short, lable in + cmd.user_options] + self.assertIn('user', options) + + # setting a value + cmd.user = True + + # user base and site shouldn't be created yet + self.assertFalse(os.path.exists(self.user_base)) + self.assertFalse(os.path.exists(self.user_site)) + + # let's run finalize + cmd.ensure_finalized() + + # now they should + self.assertTrue(os.path.exists(self.user_base)) + self.assertTrue(os.path.exists(self.user_site)) + + self.assertIn('userbase', cmd.config_vars) + self.assertIn('usersite', cmd.config_vars) + + def test_handle_extra_path(self): + dist = Distribution({'name': 'xx', 'extra_path': 'path,dirs'}) + cmd = install_dist(dist) + + # two elements + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, ['path', 'dirs']) + self.assertEqual(cmd.extra_dirs, 'dirs') + self.assertEqual(cmd.path_file, 'path') + + # one element + cmd.extra_path = ['path'] + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, ['path']) + self.assertEqual(cmd.extra_dirs, 'path') + self.assertEqual(cmd.path_file, 'path') + + # none + dist.extra_path = cmd.extra_path = None + cmd.handle_extra_path() + self.assertEqual(cmd.extra_path, None) + self.assertEqual(cmd.extra_dirs, '') + self.assertEqual(cmd.path_file, None) + + # three elements (no way !) + cmd.extra_path = 'path,dirs,again' + self.assertRaises(PackagingOptionError, cmd.handle_extra_path) + + def test_finalize_options(self): + dist = Distribution({'name': 'xx'}) + cmd = install_dist(dist) + + # must supply either prefix/exec-prefix/home or + # install-base/install-platbase -- not both + cmd.prefix = 'prefix' + cmd.install_base = 'base' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + # must supply either home or prefix/exec-prefix -- not both + cmd.install_base = None + cmd.home = 'home' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + if sys.version >= '2.6': + # can't combine user with with prefix/exec_prefix/home or + # install_(plat)base + cmd.prefix = None + cmd.user = 'user' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + def test_old_record(self): + # test pre-PEP 376 --record option (outside dist-info dir) + install_dir = self.mkdtemp() + pkgdir, dist = self.create_dist() + + dist = Distribution() + cmd = install_dist(dist) + dist.command_obj['install_dist'] = cmd + cmd.root = install_dir + cmd.record = os.path.join(pkgdir, 'filelist') + cmd.ensure_finalized() + cmd.run() + + # let's check the record file was created with four + # lines, one for each .dist-info entry: METADATA, + # INSTALLER, REQUSTED, RECORD + f = open(cmd.record) + try: + self.assertEqual(len(f.readlines()), 4) + finally: + f.close() + + # XXX test that fancy_getopt is okay with options named + # record and no-record but unrelated + + +def test_suite(): + return unittest.makeSuite(InstallTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_distinfo.py b/Lib/packaging/tests/test_command_install_distinfo.py new file mode 100644 index 0000000000..3d33691de6 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_distinfo.py @@ -0,0 +1,192 @@ +"""Tests for ``packaging.command.install_distinfo``. """ + +import os +import csv +import hashlib +import sys + +from packaging.command.install_distinfo import install_distinfo +from packaging.command.cmd import Command +from packaging.metadata import Metadata +from packaging.tests import unittest, support + + +class DummyInstallCmd(Command): + + def __init__(self, dist=None): + self.outputs = [] + self.distribution = dist + + def __getattr__(self, name): + return None + + def ensure_finalized(self): + pass + + def get_outputs(self): + return (self.outputs + + self.get_finalized_command('install_distinfo').get_outputs()) + + +class InstallDistinfoTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + checkLists = lambda self, x, y: self.assertListEqual(sorted(x), sorted(y)) + + def test_empty_install(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.ensure_finalized() + cmd.run() + + self.checkLists(os.listdir(install_dir), ['foo-1.0.dist-info']) + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'RECORD', 'REQUESTED', 'INSTALLER']) + with open(os.path.join(dist_info, 'INSTALLER')) as fp: + self.assertEqual(fp.read(), 'distutils') + with open(os.path.join(dist_info, 'REQUESTED')) as fp: + self.assertEqual(fp.read(), '') + meta_path = os.path.join(dist_info, 'METADATA') + self.assertTrue(Metadata(path=meta_path).check()) + + def test_installer(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.installer = 'bacon-python' + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + with open(os.path.join(dist_info, 'INSTALLER')) as fp: + self.assertEqual(fp.read(), 'bacon-python') + + def test_requested(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.requested = False + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'RECORD', 'INSTALLER']) + + def test_no_record(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.no_record = True + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + self.checkLists(os.listdir(dist_info), + ['METADATA', 'REQUESTED', 'INSTALLER']) + + def test_record(self): + pkg_dir, dist = self.create_dist(name='foo', + version='1.0') + install_dir = self.mkdtemp() + + install = DummyInstallCmd(dist) + dist.command_obj['install_dist'] = install + + fake_dists = os.path.join(os.path.dirname(__file__), 'fake_dists') + fake_dists = os.path.realpath(fake_dists) + + # for testing, we simply add all files from _backport's fake_dists + dirs = [] + for dir in os.listdir(fake_dists): + full_path = os.path.join(fake_dists, dir) + if (not dir.endswith('.egg') or dir.endswith('.egg-info') or + dir.endswith('.dist-info')) and os.path.isdir(full_path): + dirs.append(full_path) + + for dir in dirs: + for path, subdirs, files in os.walk(dir): + install.outputs += [os.path.join(path, f) for f in files] + install.outputs += [os.path.join('path', f + 'c') + for f in files if f.endswith('.py')] + + cmd = install_distinfo(dist) + dist.command_obj['install_distinfo'] = cmd + + cmd.initialize_options() + cmd.distinfo_dir = install_dir + cmd.ensure_finalized() + cmd.run() + + dist_info = os.path.join(install_dir, 'foo-1.0.dist-info') + + expected = [] + for f in install.get_outputs(): + if (f.endswith('.pyc') or f == os.path.join( + install_dir, 'foo-1.0.dist-info', 'RECORD')): + expected.append([f, '', '']) + else: + size = os.path.getsize(f) + md5 = hashlib.md5() + with open(f) as fp: + md5.update(fp.read().encode()) + hash = md5.hexdigest() + expected.append([f, hash, str(size)]) + + parsed = [] + with open(os.path.join(dist_info, 'RECORD'), 'r') as f: + reader = csv.reader(f, delimiter=',', + lineterminator=os.linesep, + quotechar='"') + parsed = list(reader) + + self.maxDiff = None + self.checkLists(parsed, expected) + + +def test_suite(): + return unittest.makeSuite(InstallDistinfoTestCase) + + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_headers.py b/Lib/packaging/tests/test_command_install_headers.py new file mode 100644 index 0000000000..f2906a7e18 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_headers.py @@ -0,0 +1,38 @@ +"""Tests for packaging.command.install_headers.""" +import os + +from packaging.command.install_headers import install_headers +from packaging.tests import unittest, support + + +class InstallHeadersTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_simple_run(self): + # we have two headers + header_list = self.mkdtemp() + header1 = os.path.join(header_list, 'header1') + header2 = os.path.join(header_list, 'header2') + self.write_file(header1) + self.write_file(header2) + headers = [header1, header2] + + pkg_dir, dist = self.create_dist(headers=headers) + cmd = install_headers(dist) + self.assertEqual(cmd.get_inputs(), headers) + + # let's run the command + cmd.install_dir = os.path.join(pkg_dir, 'inst') + cmd.ensure_finalized() + cmd.run() + + # let's check the results + self.assertEqual(len(cmd.get_outputs()), 2) + + +def test_suite(): + return unittest.makeSuite(InstallHeadersTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_lib.py b/Lib/packaging/tests/test_command_install_lib.py new file mode 100644 index 0000000000..99d47dd621 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_lib.py @@ -0,0 +1,111 @@ +"""Tests for packaging.command.install_data.""" +import sys +import os + +from packaging.tests import unittest, support +from packaging.command.install_lib import install_lib +from packaging.compiler.extension import Extension +from packaging.errors import PackagingOptionError + +try: + no_bytecode = sys.dont_write_bytecode + bytecode_support = True +except AttributeError: + no_bytecode = False + bytecode_support = False + + +class InstallLibTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['PYTHONPATH'] + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + cmd.finalize_options() + self.assertTrue(cmd.compile) + self.assertEqual(cmd.optimize, 0) + + # optimize must be 0, 1, or 2 + cmd.optimize = 'foo' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + cmd.optimize = '4' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + cmd.optimize = '2' + cmd.finalize_options() + self.assertEqual(cmd.optimize, 2) + + @unittest.skipIf(no_bytecode, 'byte-compile not supported') + def test_byte_compile(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + cmd.compile = True + cmd.optimize = 1 + + f = os.path.join(pkg_dir, 'foo.py') + self.write_file(f, '# python file') + cmd.byte_compile([f]) + self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyc'))) + self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'foo.pyo'))) + + def test_get_outputs(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + # setting up a dist environment + cmd.compile = True + cmd.optimize = 1 + cmd.install_dir = pkg_dir + f = os.path.join(pkg_dir, '__init__.py') + self.write_file(f, '# python package') + cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] + cmd.distribution.packages = [pkg_dir] + cmd.distribution.script_name = 'setup.py' + + # get_output should return 4 elements + self.assertEqual(len(cmd.get_outputs()), 4) + + def test_get_inputs(self): + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + + # setting up a dist environment + cmd.compile = True + cmd.optimize = 1 + cmd.install_dir = pkg_dir + f = os.path.join(pkg_dir, '__init__.py') + self.write_file(f, '# python package') + cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] + cmd.distribution.packages = [pkg_dir] + cmd.distribution.script_name = 'setup.py' + + # get_input should return 2 elements + self.assertEqual(len(cmd.get_inputs()), 2) + + @unittest.skipUnless(bytecode_support, + 'sys.dont_write_bytecode not supported') + def test_dont_write_bytecode(self): + # makes sure byte_compile is not used + pkg_dir, dist = self.create_dist() + cmd = install_lib(dist) + cmd.compile = True + cmd.optimize = 1 + + self.addCleanup(setattr, sys, 'dont_write_bytecode', + sys.dont_write_bytecode) + sys.dont_write_bytecode = True + cmd.byte_compile([]) + + self.assertIn('byte-compiling is disabled', self.get_logs()[0]) + + +def test_suite(): + return unittest.makeSuite(InstallLibTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_install_scripts.py b/Lib/packaging/tests/test_command_install_scripts.py new file mode 100644 index 0000000000..08c7338e34 --- /dev/null +++ b/Lib/packaging/tests/test_command_install_scripts.py @@ -0,0 +1,78 @@ +"""Tests for packaging.command.install_scripts.""" +import os + +from packaging.tests import unittest, support +from packaging.command.install_scripts import install_scripts +from packaging.dist import Distribution + + +class InstallScriptsTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def test_default_settings(self): + dist = Distribution() + dist.command_obj["build"] = support.DummyCommand( + build_scripts="/foo/bar") + dist.command_obj["install_dist"] = support.DummyCommand( + install_scripts="/splat/funk", + force=True, + skip_build=True, + ) + cmd = install_scripts(dist) + self.assertFalse(cmd.force) + self.assertFalse(cmd.skip_build) + self.assertIs(cmd.build_dir, None) + self.assertIs(cmd.install_dir, None) + + cmd.finalize_options() + + self.assertTrue(cmd.force) + self.assertTrue(cmd.skip_build) + self.assertEqual(cmd.build_dir, "/foo/bar") + self.assertEqual(cmd.install_dir, "/splat/funk") + + def test_installation(self): + source = self.mkdtemp() + expected = [] + + def write_script(name, text): + expected.append(name) + f = open(os.path.join(source, name), "w") + try: + f.write(text) + finally: + f.close() + + write_script("script1.py", ("#! /usr/bin/env python2.3\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + write_script("script2.py", ("#!/usr/bin/python\n" + "# bogus script w/ Python sh-bang\n" + "pass\n")) + write_script("shell.sh", ("#!/bin/sh\n" + "# bogus shell script w/ sh-bang\n" + "exit 0\n")) + + target = self.mkdtemp() + dist = Distribution() + dist.command_obj["build"] = support.DummyCommand(build_scripts=source) + dist.command_obj["install_dist"] = support.DummyCommand( + install_scripts=target, + force=True, + skip_build=True, + ) + cmd = install_scripts(dist) + cmd.finalize_options() + cmd.run() + + installed = os.listdir(target) + for name in expected: + self.assertIn(name, installed) + + +def test_suite(): + return unittest.makeSuite(InstallScriptsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_register.py b/Lib/packaging/tests/test_command_register.py new file mode 100644 index 0000000000..7aa487ae5b --- /dev/null +++ b/Lib/packaging/tests/test_command_register.py @@ -0,0 +1,259 @@ +"""Tests for packaging.command.register.""" +import os +import getpass +import urllib.request +import urllib.error +import urllib.parse + +try: + import docutils + DOCUTILS_SUPPORT = True +except ImportError: + DOCUTILS_SUPPORT = False + +from packaging.tests import unittest, support +from packaging.command import register as register_module +from packaging.command.register import register +from packaging.errors import PackagingSetupError + + +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + +WANTED_PYPIRC = """\ +[distutils] +index-servers = + pypi + +[pypi] +username:tarek +password:password +""" + + +class Inputs: + """Fakes user inputs.""" + def __init__(self, *answers): + self.answers = answers + self.index = 0 + + def __call__(self, prompt=''): + try: + return self.answers[self.index] + finally: + self.index += 1 + + +class FakeOpener: + """Fakes a PyPI server""" + def __init__(self): + self.reqs = [] + + def __call__(self, *args): + return self + + def open(self, req): + self.reqs.append(req) + return self + + def read(self): + return 'xxx' + + +class RegisterTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(RegisterTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + + # patching the password prompt + self._old_getpass = getpass.getpass + + def _getpass(prompt): + return 'password' + + getpass.getpass = _getpass + self.old_opener = urllib.request.build_opener + self.conn = urllib.request.build_opener = FakeOpener() + + def tearDown(self): + getpass.getpass = self._old_getpass + urllib.request.build_opener = self.old_opener + if hasattr(register_module, 'input'): + del register_module.input + super(RegisterTestCase, self).tearDown() + + def _get_cmd(self, metadata=None): + if metadata is None: + metadata = {'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': 'xxx'} + pkg_info, dist = self.create_dist(**metadata) + return register(dist) + + def test_create_pypirc(self): + # this test makes sure a .pypirc file + # is created when requested. + + # let's create a register instance + cmd = self._get_cmd() + + # we shouldn't have a .pypirc file yet + self.assertFalse(os.path.exists(self.rc)) + + # patching input and getpass.getpass + # so register gets happy + # Here's what we are faking : + # use your existing login (choice 1.) + # Username : 'tarek' + # Password : 'password' + # Save your login (y/N)? : 'y' + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # we should have a brand new .pypirc file + self.assertTrue(os.path.exists(self.rc)) + + # with the content similar to WANTED_PYPIRC + with open(self.rc) as fp: + content = fp.read() + self.assertEqual(content, WANTED_PYPIRC) + + # now let's make sure the .pypirc file generated + # really works : we shouldn't be asked anything + # if we run the command again + def _no_way(prompt=''): + raise AssertionError(prompt) + + register_module.input = _no_way + cmd.show_response = True + cmd.ensure_finalized() + cmd.run() + + # let's see what the server received : we should + # have 2 similar requests + self.assertEqual(len(self.conn.reqs), 2) + req1 = dict(self.conn.reqs[0].headers) + req2 = dict(self.conn.reqs[1].headers) + self.assertEqual(req2['Content-length'], req1['Content-length']) + self.assertIn('xxx', self.conn.reqs[1].data) + + def test_password_not_in_file(self): + + self.write_file(self.rc, PYPIRC_NOPASSWORD) + cmd = self._get_cmd() + cmd.finalize_options() + cmd._set_config() + cmd.send_metadata() + + # dist.password should be set + # therefore used afterwards by other commands + self.assertEqual(cmd.distribution.password, 'password') + + def test_registration(self): + # this test runs choice 2 + cmd = self._get_cmd() + inputs = Inputs('2', 'tarek', 'tarek@ziade.org') + register_module.input = inputs + # let's run the command + # FIXME does this send a real request? use a mock server + cmd.ensure_finalized() + cmd.run() + + # we should have send a request + self.assertEqual(len(self.conn.reqs), 1) + req = self.conn.reqs[0] + headers = dict(req.headers) + self.assertEqual(headers['Content-length'], '608') + self.assertIn('tarek', req.data) + + def test_password_reset(self): + # this test runs choice 3 + cmd = self._get_cmd() + inputs = Inputs('3', 'tarek@ziade.org') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # we should have send a request + self.assertEqual(len(self.conn.reqs), 1) + req = self.conn.reqs[0] + headers = dict(req.headers) + self.assertEqual(headers['Content-length'], '290') + self.assertIn('tarek', req.data) + + @unittest.skipUnless(DOCUTILS_SUPPORT, 'needs docutils') + def test_strict(self): + # testing the script option + # when on, the register command stops if + # the metadata is incomplete or if + # long_description is not reSt compliant + + # empty metadata + cmd = self._get_cmd({'name': 'xxx', 'version': 'xxx'}) + cmd.ensure_finalized() + cmd.strict = True + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + self.assertRaises(PackagingSetupError, cmd.run) + + # metadata is OK but long_description is broken + metadata = {'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'éxéxé', + 'name': 'xxx', 'version': 'xxx', + 'description': 'title\n==\n\ntext'} + + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + + self.assertRaises(PackagingSetupError, cmd.run) + + # now something that works + metadata['description'] = 'title\n=====\n\ntext' + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + # strict is not by default + cmd = self._get_cmd() + cmd.ensure_finalized() + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs + cmd.ensure_finalized() + cmd.run() + + def test_register_pep345(self): + cmd = self._get_cmd({}) + cmd.ensure_finalized() + cmd.distribution.metadata['Requires-Dist'] = ['lxml'] + data = cmd.build_post_data('submit') + self.assertEqual(data['metadata_version'], '1.2') + self.assertEqual(data['requires_dist'], ['lxml']) + + +def test_suite(): + return unittest.makeSuite(RegisterTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_sdist.py b/Lib/packaging/tests/test_command_sdist.py new file mode 100644 index 0000000000..956e258366 --- /dev/null +++ b/Lib/packaging/tests/test_command_sdist.py @@ -0,0 +1,407 @@ +"""Tests for packaging.command.sdist.""" +import os +import zipfile +import tarfile +import logging + +# zlib is not used here, but if it's not available +# the tests that use zipfile may fail +try: + import zlib +except ImportError: + zlib = None + +try: + import grp + import pwd + UID_GID_SUPPORT = True +except ImportError: + UID_GID_SUPPORT = False + +from os.path import join +from packaging.tests import captured_stdout +from packaging.command.sdist import sdist +from packaging.command.sdist import show_formats +from packaging.dist import Distribution +from packaging.tests import unittest +from packaging.errors import PackagingOptionError +from packaging.util import find_executable +from packaging.tests import support +from shutil import get_archive_formats + +SETUP_PY = """ +from packaging.core import setup +import somecode + +setup(name='fake') +""" + +MANIFEST = """\ +# file GENERATED by packaging, do NOT edit +README +inroot.txt +data%(sep)sdata.dt +scripts%(sep)sscript.py +some%(sep)sfile.txt +some%(sep)sother_file.txt +somecode%(sep)s__init__.py +somecode%(sep)sdoc.dat +somecode%(sep)sdoc.txt +""" + + +def builder(dist, filelist): + filelist.append('bah') + + +class SDistTestCase(support.TempdirManager, + support.LoggingCatcher, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['HOME'] + + def setUp(self): + # PyPIRCCommandTestCase creates a temp dir already + # and put it in self.tmp_dir + super(SDistTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + os.environ['HOME'] = self.tmp_dir + # setting up an environment + self.old_path = os.getcwd() + os.mkdir(join(self.tmp_dir, 'somecode')) + os.mkdir(join(self.tmp_dir, 'dist')) + # a package, and a README + self.write_file((self.tmp_dir, 'README'), 'xxx') + self.write_file((self.tmp_dir, 'somecode', '__init__.py'), '#') + self.write_file((self.tmp_dir, 'setup.py'), SETUP_PY) + os.chdir(self.tmp_dir) + + def tearDown(self): + # back to normal + os.chdir(self.old_path) + super(SDistTestCase, self).tearDown() + + def get_cmd(self, metadata=None): + """Returns a cmd""" + if metadata is None: + metadata = {'name': 'fake', 'version': '1.0', + 'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx'} + dist = Distribution(metadata) + dist.script_name = 'setup.py' + dist.packages = ['somecode'] + dist.include_package_data = True + cmd = sdist(dist) + cmd.dist_dir = 'dist' + return dist, cmd + + @unittest.skipUnless(zlib, "requires zlib") + def test_prune_file_list(self): + # this test creates a package with some vcs dirs in it + # and launch sdist to make sure they get pruned + # on all systems + + # creating VCS directories with some files in them + os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) + + self.write_file((self.tmp_dir, 'somecode', '.svn', 'ok.py'), 'xxx') + + os.mkdir(join(self.tmp_dir, 'somecode', '.hg')) + self.write_file((self.tmp_dir, 'somecode', '.hg', + 'ok'), 'xxx') + + os.mkdir(join(self.tmp_dir, 'somecode', '.git')) + self.write_file((self.tmp_dir, 'somecode', '.git', + 'ok'), 'xxx') + + # now building a sdist + dist, cmd = self.get_cmd() + + # zip is available universally + # (tar might not be installed under win32) + cmd.formats = ['zip'] + + cmd.ensure_finalized() + cmd.run() + + # now let's check what we have + dist_folder = join(self.tmp_dir, 'dist') + files = os.listdir(dist_folder) + self.assertEqual(files, ['fake-1.0.zip']) + + with zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) as zip_file: + content = zip_file.namelist() + + # making sure everything has been pruned correctly + self.assertEqual(len(content), 3) + + @unittest.skipUnless(zlib, "requires zlib") + @unittest.skipIf(find_executable('tar') is None or + find_executable('gzip') is None, + 'requires tar and gzip programs') + def test_make_distribution(self): + # building a sdist + dist, cmd = self.get_cmd() + + # creating a gztar then a tar + cmd.formats = ['gztar', 'tar'] + cmd.ensure_finalized() + cmd.run() + + # making sure we have two files + dist_folder = join(self.tmp_dir, 'dist') + result = sorted(os.listdir(dist_folder)) + self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + + os.remove(join(dist_folder, 'fake-1.0.tar')) + os.remove(join(dist_folder, 'fake-1.0.tar.gz')) + + # now trying a tar then a gztar + cmd.formats = ['tar', 'gztar'] + + cmd.ensure_finalized() + cmd.run() + + result = sorted(os.listdir(dist_folder)) + self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + + @unittest.skipUnless(zlib, "requires zlib") + def test_add_defaults(self): + + # http://bugs.python.org/issue2279 + + # add_default should also include + # data_files and package_data + dist, cmd = self.get_cmd() + + # filling data_files by pointing files + # in package_data + dist.package_data = {'': ['*.cfg', '*.dat'], + 'somecode': ['*.txt']} + self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#') + self.write_file((self.tmp_dir, 'somecode', 'doc.dat'), '#') + + # adding some data in data_files + data_dir = join(self.tmp_dir, 'data') + os.mkdir(data_dir) + self.write_file((data_dir, 'data.dt'), '#') + some_dir = join(self.tmp_dir, 'some') + os.mkdir(some_dir) + self.write_file((self.tmp_dir, 'inroot.txt'), '#') + self.write_file((some_dir, 'file.txt'), '#') + self.write_file((some_dir, 'other_file.txt'), '#') + + dist.data_files = {'data/data.dt': '{appdata}/data.dt', + 'inroot.txt': '{appdata}/inroot.txt', + 'some/file.txt': '{appdata}/file.txt', + 'some/other_file.txt': '{appdata}/other_file.txt'} + + # adding a script + script_dir = join(self.tmp_dir, 'scripts') + os.mkdir(script_dir) + self.write_file((script_dir, 'script.py'), '#') + dist.scripts = [join('scripts', 'script.py')] + + cmd.formats = ['zip'] + cmd.use_defaults = True + + cmd.ensure_finalized() + cmd.run() + + # now let's check what we have + dist_folder = join(self.tmp_dir, 'dist') + files = os.listdir(dist_folder) + self.assertEqual(files, ['fake-1.0.zip']) + + with zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) as zip_file: + content = zip_file.namelist() + + # Making sure everything was added. This includes 9 code and data + # files in addition to PKG-INFO. + self.assertEqual(len(content), 10) + + # Checking the MANIFEST + with open(join(self.tmp_dir, 'MANIFEST')) as fp: + manifest = fp.read() + self.assertEqual(manifest, MANIFEST % {'sep': os.sep}) + + @unittest.skipUnless(zlib, "requires zlib") + def test_metadata_check_option(self): + # testing the `check-metadata` option + dist, cmd = self.get_cmd(metadata={'name': 'xxx', 'version': 'xxx'}) + + # this should raise some warnings + # with the check subcommand + cmd.ensure_finalized() + cmd.run() + warnings = self.get_logs(logging.WARN) + self.assertEqual(len(warnings), 3) + + # trying with a complete set of metadata + self.loghandler.flush() + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + cmd.metadata_check = False + cmd.run() + warnings = self.get_logs(logging.WARN) + # removing manifest generated warnings + warnings = [warn for warn in warnings if + not warn.endswith('-- skipping')] + # the remaining warning is about the use of the default file list + self.assertEqual(len(warnings), 1) + + def test_show_formats(self): + __, stdout = captured_stdout(show_formats) + + # the output should be a header line + one line per format + num_formats = len(get_archive_formats()) + output = [line for line in stdout.split('\n') + if line.strip().startswith('--formats=')] + self.assertEqual(len(output), num_formats) + + def test_finalize_options(self): + + dist, cmd = self.get_cmd() + cmd.finalize_options() + + # default options set by finalize + self.assertEqual(cmd.manifest, 'MANIFEST') + self.assertEqual(cmd.dist_dir, 'dist') + + # formats has to be a string splitable on (' ', ',') or + # a stringlist + cmd.formats = 1 + self.assertRaises(PackagingOptionError, cmd.finalize_options) + cmd.formats = ['zip'] + cmd.finalize_options() + + # formats has to be known + cmd.formats = 'supazipa' + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + @unittest.skipUnless(zlib, "requires zlib") + @unittest.skipUnless(UID_GID_SUPPORT, "requires grp and pwd support") + @unittest.skipIf(find_executable('tar') is None or + find_executable('gzip') is None, + 'requires tar and gzip programs') + def test_make_distribution_owner_group(self): + # building a sdist + dist, cmd = self.get_cmd() + + # creating a gztar and specifying the owner+group + cmd.formats = ['gztar'] + cmd.owner = pwd.getpwuid(0)[0] + cmd.group = grp.getgrgid(0)[0] + cmd.ensure_finalized() + cmd.run() + + # making sure we have the good rights + archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') + with tarfile.open(archive_name) as archive: + for member in archive.getmembers(): + self.assertEqual(member.uid, 0) + self.assertEqual(member.gid, 0) + + # building a sdist again + dist, cmd = self.get_cmd() + + # creating a gztar + cmd.formats = ['gztar'] + cmd.ensure_finalized() + cmd.run() + + # making sure we have the good rights + archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') + with tarfile.open(archive_name) as archive: + + # note that we are not testing the group ownership here + # because, depending on the platforms and the container + # rights (see #7408) + for member in archive.getmembers(): + self.assertEqual(member.uid, os.getuid()) + + def test_get_file_list(self): + # make sure MANIFEST is recalculated + dist, cmd = self.get_cmd() + # filling data_files by pointing files in package_data + dist.package_data = {'somecode': ['*.txt']} + self.write_file((self.tmp_dir, 'somecode', 'doc.txt'), '#') + cmd.ensure_finalized() + cmd.run() + + # Should produce four lines. Those lines are one comment, one default + # (README) and two package files. + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + self.assertEqual(len(manifest), 4) + + # Adding a file + self.write_file((self.tmp_dir, 'somecode', 'doc2.txt'), '#') + + # make sure build_py is reinitialized, like a fresh run + build_py = dist.get_command_obj('build_py') + build_py.finalized = False + build_py.ensure_finalized() + + cmd.run() + + with open(cmd.manifest) as f: + manifest2 = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + # Do we have the new file in MANIFEST? + self.assertEqual(len(manifest2), 5) + self.assertIn('doc2.txt', manifest2[-1]) + + def test_manifest_marker(self): + # check that autogenerated MANIFESTs have a marker + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + cmd.run() + + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + self.assertEqual(manifest[0], + '# file GENERATED by packaging, do NOT edit') + + def test_manual_manifest(self): + # check that a MANIFEST without a marker is left alone + dist, cmd = self.get_cmd() + cmd.ensure_finalized() + self.write_file((self.tmp_dir, cmd.manifest), 'README.manual') + cmd.run() + + with open(cmd.manifest) as f: + manifest = [line.strip() for line in f.read().split('\n') + if line.strip() != ''] + + self.assertEqual(manifest, ['README.manual']) + + def test_template(self): + dist, cmd = self.get_cmd() + dist.extra_files = ['include yeah'] + cmd.ensure_finalized() + self.write_file((self.tmp_dir, 'yeah'), 'xxx') + cmd.run() + with open(cmd.manifest) as f: + content = f.read() + + self.assertIn('yeah', content) + + def test_manifest_builder(self): + dist, cmd = self.get_cmd() + cmd.manifest_builders = 'packaging.tests.test_command_sdist.builder' + cmd.ensure_finalized() + cmd.run() + self.assertIn('bah', cmd.filelist.files) + + +def test_suite(): + return unittest.makeSuite(SDistTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_test.py b/Lib/packaging/tests/test_command_test.py new file mode 100644 index 0000000000..4fd8452683 --- /dev/null +++ b/Lib/packaging/tests/test_command_test.py @@ -0,0 +1,225 @@ +import os +import re +import sys +import shutil +import logging +import unittest as ut1 +import packaging.database + +from os.path import join +from operator import getitem, setitem, delitem +from packaging.command.build import build +from packaging.tests import unittest +from packaging.tests.support import (TempdirManager, EnvironRestorer, + LoggingCatcher) +from packaging.command.test import test +from packaging.command import set_command +from packaging.dist import Distribution + + +EXPECTED_OUTPUT_RE = r'''FAIL: test_blah \(myowntestmodule.SomeTest\) +---------------------------------------------------------------------- +Traceback \(most recent call last\): + File ".+/myowntestmodule.py", line \d+, in test_blah + self.fail\("horribly"\) +AssertionError: horribly +''' + +here = os.path.dirname(os.path.abspath(__file__)) + + +class MockBuildCmd(build): + build_lib = "mock build lib" + command_name = 'build' + plat_name = 'whatever' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + self._record.append("build has run") + + +class TestTest(TempdirManager, + EnvironRestorer, + LoggingCatcher, + unittest.TestCase): + + restore_environ = ['PYTHONPATH'] + + def setUp(self): + super(TestTest, self).setUp() + self.addCleanup(packaging.database.clear_cache) + new_pythonpath = os.path.dirname(os.path.dirname(here)) + pythonpath = os.environ.get('PYTHONPATH') + if pythonpath is not None: + new_pythonpath = os.pathsep.join((new_pythonpath, pythonpath)) + os.environ['PYTHONPATH'] = new_pythonpath + + def assert_re_match(self, pattern, string): + def quote(s): + lines = ['## ' + line for line in s.split('\n')] + sep = ["#" * 60] + return [''] + sep + lines + sep + msg = quote(pattern) + ["didn't match"] + quote(string) + msg = "\n".join(msg) + if not re.search(pattern, string): + self.fail(msg) + + def prepare_dist(self, dist_name): + pkg_dir = join(os.path.dirname(__file__), "dists", dist_name) + temp_pkg_dir = join(self.mkdtemp(), dist_name) + shutil.copytree(pkg_dir, temp_pkg_dir) + return temp_pkg_dir + + def safely_replace(self, obj, attr, + new_val=None, delete=False, dictionary=False): + """Replace a object's attribute returning to its original state at the + end of the test run. Creates the attribute if not present before + (deleting afterwards). When delete=True, makes sure the value is del'd + for the test run. If dictionary is set to True, operates of its items + rather than attributes.""" + if dictionary: + _setattr, _getattr, _delattr = setitem, getitem, delitem + + def _hasattr(_dict, value): + return value in _dict + else: + _setattr, _getattr, _delattr, _hasattr = (setattr, getattr, + delattr, hasattr) + + orig_has_attr = _hasattr(obj, attr) + if orig_has_attr: + orig_val = _getattr(obj, attr) + + if delete is False: + _setattr(obj, attr, new_val) + elif orig_has_attr: + _delattr(obj, attr) + + def do_cleanup(): + if orig_has_attr: + _setattr(obj, attr, orig_val) + elif _hasattr(obj, attr): + _delattr(obj, attr) + + self.addCleanup(do_cleanup) + + def test_runs_unittest(self): + module_name, a_module = self.prepare_a_module() + record = [] + a_module.recorder = lambda *args: record.append("suite") + + class MockTextTestRunner: + def __init__(*_, **__): + pass + + def run(_self, suite): + record.append("run") + + self.safely_replace(ut1, "TextTestRunner", MockTextTestRunner) + + dist = Distribution() + cmd = test(dist) + cmd.suite = "%s.recorder" % module_name + cmd.run() + self.assertEqual(record, ["suite", "run"]) + + def test_builds_before_running_tests(self): + self.addCleanup(set_command, 'packaging.command.build.build') + set_command('packaging.tests.test_command_test.MockBuildCmd') + + dist = Distribution() + dist.get_command_obj('build')._record = record = [] + cmd = test(dist) + cmd.runner = self.prepare_named_function(lambda: None) + cmd.ensure_finalized() + cmd.run() + self.assertEqual(['build has run'], record) + + def _test_works_with_2to3(self): + pass + + def test_checks_requires(self): + dist = Distribution() + cmd = test(dist) + phony_project = 'ohno_ohno-impossible_1234-name_stop-that!' + cmd.tests_require = [phony_project] + cmd.ensure_finalized() + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn(phony_project, logs[0]) + + def prepare_a_module(self): + tmp_dir = self.mkdtemp() + sys.path.append(tmp_dir) + self.addCleanup(sys.path.remove, tmp_dir) + + self.write_file((tmp_dir, 'packaging_tests_a.py'), '') + import packaging_tests_a as a_module + return "packaging_tests_a", a_module + + def prepare_named_function(self, func): + module_name, a_module = self.prepare_a_module() + a_module.recorder = func + return "%s.recorder" % module_name + + def test_custom_runner(self): + dist = Distribution() + cmd = test(dist) + record = [] + cmd.runner = self.prepare_named_function( + lambda: record.append("runner called")) + cmd.ensure_finalized() + cmd.run() + self.assertEqual(["runner called"], record) + + def prepare_mock_ut2(self): + class MockUTClass: + def __init__(*_, **__): + pass + + def discover(self): + pass + + def run(self, _): + pass + + class MockUTModule: + TestLoader = MockUTClass + TextTestRunner = MockUTClass + + mock_ut2 = MockUTModule() + self.safely_replace(sys.modules, "unittest2", + mock_ut2, dictionary=True) + return mock_ut2 + + def test_gets_unittest_discovery(self): + mock_ut2 = self.prepare_mock_ut2() + dist = Distribution() + cmd = test(dist) + self.safely_replace(ut1.TestLoader, "discover", lambda: None) + self.assertEqual(cmd.get_ut_with_discovery(), ut1) + + del ut1.TestLoader.discover + self.assertEqual(cmd.get_ut_with_discovery(), mock_ut2) + + def test_calls_discover(self): + self.safely_replace(ut1.TestLoader, "discover", delete=True) + mock_ut2 = self.prepare_mock_ut2() + record = [] + mock_ut2.TestLoader.discover = lambda self, path: record.append(path) + dist = Distribution() + cmd = test(dist) + cmd.run() + self.assertEqual([os.curdir], record) + + +def test_suite(): + return unittest.makeSuite(TestTest) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_upload.py b/Lib/packaging/tests/test_command_upload.py new file mode 100644 index 0000000000..f2e338b9dd --- /dev/null +++ b/Lib/packaging/tests/test_command_upload.py @@ -0,0 +1,157 @@ +"""Tests for packaging.command.upload.""" +import os +import sys + +from packaging.command.upload import upload +from packaging.dist import Distribution +from packaging.errors import PackagingOptionError + +from packaging.tests import unittest, support +from packaging.tests.pypi_server import PyPIServer, PyPIServerTestCase + + +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + +PYPIRC = """\ +[distutils] + +index-servers = + server1 + server2 + +[server1] +username:me +password:secret + +[server2] +username:meagain +password: secret +realm:acme +repository:http://another.pypi/ +""" + + +class UploadTestCase(support.TempdirManager, support.EnvironRestorer, + support.LoggingCatcher, PyPIServerTestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(UploadTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + + def test_finalize_options(self): + # new format + self.write_file(self.rc, PYPIRC) + dist = Distribution() + cmd = upload(dist) + cmd.finalize_options() + for attr, expected in (('username', 'me'), ('password', 'secret'), + ('realm', 'pypi'), + ('repository', 'http://pypi.python.org/pypi')): + self.assertEqual(getattr(cmd, attr), expected) + + def test_finalize_options_unsigned_identity_raises_exception(self): + self.write_file(self.rc, PYPIRC) + dist = Distribution() + cmd = upload(dist) + cmd.identity = True + cmd.sign = False + self.assertRaises(PackagingOptionError, cmd.finalize_options) + + def test_saved_password(self): + # file with no password + self.write_file(self.rc, PYPIRC_NOPASSWORD) + + # make sure it passes + dist = Distribution() + cmd = upload(dist) + cmd.ensure_finalized() + self.assertEqual(cmd.password, None) + + # make sure we get it as well, if another command + # initialized it at the dist level + dist.password = 'xxx' + cmd = upload(dist) + cmd.finalize_options() + self.assertEqual(cmd.password, 'xxx') + + def test_upload_without_files_raises_exception(self): + dist = Distribution() + cmd = upload(dist) + self.assertRaises(PackagingOptionError, cmd.run) + + def test_upload(self): + path = os.path.join(self.tmp_dir, 'xxx') + self.write_file(path) + command, pyversion, filename = 'xxx', '3.3', path + dist_files = [(command, pyversion, filename)] + + # lets run it + pkg_dir, dist = self.create_dist(dist_files=dist_files, author='dédé') + cmd = upload(dist) + cmd.ensure_finalized() + cmd.repository = self.pypi.full_address + cmd.run() + + # what did we send ? + handler, request_data = self.pypi.requests[-1] + headers = handler.headers + #self.assertIn('dédé', str(request_data)) + self.assertIn(b'xxx', request_data) + + self.assertEqual(int(headers['content-length']), len(request_data)) + self.assertLess(int(headers['content-length']), 2500) + self.assertTrue(headers['content-type'].startswith('multipart/form-data')) + self.assertEqual(handler.command, 'POST') + self.assertNotIn('\n', headers['authorization']) + + def test_upload_docs(self): + path = os.path.join(self.tmp_dir, 'xxx') + self.write_file(path) + command, pyversion, filename = 'xxx', '3.3', path + dist_files = [(command, pyversion, filename)] + docs_path = os.path.join(self.tmp_dir, "build", "docs") + os.makedirs(docs_path) + self.write_file(os.path.join(docs_path, "index.html"), "yellow") + self.write_file(self.rc, PYPIRC) + + # lets run it + pkg_dir, dist = self.create_dist(dist_files=dist_files, author='dédé') + + cmd = upload(dist) + cmd.get_finalized_command("build").run() + cmd.upload_docs = True + cmd.ensure_finalized() + cmd.repository = self.pypi.full_address + try: + prev_dir = os.getcwd() + os.chdir(self.tmp_dir) + cmd.run() + finally: + os.chdir(prev_dir) + + handler, request_data = self.pypi.requests[-1] + action, name, content = request_data.split( + "----------------GHSKFJDLGDS7543FJKLFHRE75642756743254" + .encode())[1:4] + + self.assertIn(b'name=":action"', action) + self.assertIn(b'doc_upload', action) + + +def test_suite(): + return unittest.makeSuite(UploadTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_command_upload_docs.py b/Lib/packaging/tests/test_command_upload_docs.py new file mode 100644 index 0000000000..b103894374 --- /dev/null +++ b/Lib/packaging/tests/test_command_upload_docs.py @@ -0,0 +1,205 @@ +"""Tests for packaging.command.upload_docs.""" +import os +import sys +import shutil +import zipfile + +from packaging.command import upload_docs as upload_docs_mod +from packaging.command.upload_docs import (upload_docs, zip_dir, + encode_multipart) +from packaging.dist import Distribution +from packaging.errors import PackagingFileError, PackagingOptionError + +from packaging.tests import unittest, support +from packaging.tests.pypi_server import PyPIServerTestCase + + +EXPECTED_MULTIPART_OUTPUT = [ + b'---x', + b'Content-Disposition: form-data; name="username"', + b'', + b'wok', + b'---x', + b'Content-Disposition: form-data; name="password"', + b'', + b'secret', + b'---x', + b'Content-Disposition: form-data; name="picture"; filename="wok.png"', + b'', + b'PNG89', + b'---x--', + b'', +] + +PYPIRC = """\ +[distutils] +index-servers = server1 + +[server1] +repository = %s +username = real_slim_shady +password = long_island +""" + +class UploadDocsTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + PyPIServerTestCase): + + restore_environ = ['HOME'] + + def setUp(self): + super(UploadDocsTestCase, self).setUp() + self.tmp_dir = self.mkdtemp() + self.rc = os.path.join(self.tmp_dir, '.pypirc') + os.environ['HOME'] = self.tmp_dir + self.dist = Distribution() + self.dist.metadata['Name'] = "distr-name" + self.cmd = upload_docs(self.dist) + + def test_default_uploaddir(self): + sandbox = self.mkdtemp() + previous = os.getcwd() + os.chdir(sandbox) + try: + os.mkdir("build") + self.prepare_sample_dir("build") + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.upload_dir, os.path.join("build", "docs")) + finally: + os.chdir(previous) + + def test_default_uploaddir_looks_for_doc_also(self): + sandbox = self.mkdtemp() + previous = os.getcwd() + os.chdir(sandbox) + try: + os.mkdir("build") + self.prepare_sample_dir("build") + os.rename(os.path.join("build", "docs"), os.path.join("build", "doc")) + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.upload_dir, os.path.join("build", "doc")) + finally: + os.chdir(previous) + + def prepare_sample_dir(self, sample_dir=None): + if sample_dir is None: + sample_dir = self.mkdtemp() + os.mkdir(os.path.join(sample_dir, "docs")) + self.write_file(os.path.join(sample_dir, "docs", "index.html"), "Ce mortel ennui") + self.write_file(os.path.join(sample_dir, "index.html"), "Oh la la") + return sample_dir + + def test_zip_dir(self): + source_dir = self.prepare_sample_dir() + compressed = zip_dir(source_dir) + + zip_f = zipfile.ZipFile(compressed) + self.assertEqual(zip_f.namelist(), ['index.html', 'docs/index.html']) + + def test_encode_multipart(self): + fields = [('username', 'wok'), ('password', 'secret')] + files = [('picture', 'wok.png', b'PNG89')] + content_type, body = encode_multipart(fields, files, b'-x') + self.assertEqual(b'multipart/form-data; boundary=-x', content_type) + self.assertEqual(EXPECTED_MULTIPART_OUTPUT, body.split(b'\r\n')) + + def prepare_command(self): + self.cmd.upload_dir = self.prepare_sample_dir() + self.cmd.ensure_finalized() + self.cmd.repository = self.pypi.full_address + self.cmd.username = "username" + self.cmd.password = "password" + + def test_upload(self): + self.prepare_command() + self.cmd.run() + + self.assertEqual(len(self.pypi.requests), 1) + handler, request_data = self.pypi.requests[-1] + self.assertIn(b"content", request_data) + self.assertIn("Basic", handler.headers['authorization']) + self.assertTrue(handler.headers['content-type'] + .startswith('multipart/form-data;')) + + action, name, version, content =\ + request_data.split("----------------GHSKFJDLGDS7543FJKLFHRE75642756743254".encode())[1:5] + + + # check that we picked the right chunks + self.assertIn(b'name=":action"', action) + self.assertIn(b'name="name"', name) + self.assertIn(b'name="version"', version) + self.assertIn(b'name="content"', content) + + # check their contents + self.assertIn(b'doc_upload', action) + self.assertIn(b'distr-name', name) + self.assertIn(b'docs/index.html', content) + self.assertIn(b'Ce mortel ennui', content) + + def test_https_connection(self): + https_called = False + + orig_https = upload_docs_mod.http.client.HTTPConnection + + def https_conn_wrapper(*args): + nonlocal https_called + https_called = True + # the testing server is http + return upload_docs_mod.http.client.HTTPConnection(*args) + + upload_docs_mod.http.client.HTTPSConnection = https_conn_wrapper + try: + self.prepare_command() + self.cmd.run() + self.assertFalse(https_called) + + self.cmd.repository = self.cmd.repository.replace("http", "https") + self.cmd.run() + self.assertTrue(https_called) + finally: + upload_docs_mod.http.client.HTTPConnection = orig_https + + def test_handling_response(self): + self.pypi.default_response_status = '403 Forbidden' + self.prepare_command() + self.cmd.run() + self.assertIn('Upload failed (403): Forbidden', self.get_logs()[-1]) + + self.pypi.default_response_status = '301 Moved Permanently' + self.pypi.default_response_headers.append(("Location", "brand_new_location")) + self.cmd.run() + self.assertIn('brand_new_location', self.get_logs()[-1]) + + def test_reads_pypirc_data(self): + self.write_file(self.rc, PYPIRC % self.pypi.full_address) + self.cmd.repository = self.pypi.full_address + self.cmd.upload_dir = self.prepare_sample_dir() + self.cmd.ensure_finalized() + self.assertEqual(self.cmd.username, "real_slim_shady") + self.assertEqual(self.cmd.password, "long_island") + + def test_checks_index_html_presence(self): + self.cmd.upload_dir = self.prepare_sample_dir() + os.remove(os.path.join(self.cmd.upload_dir, "index.html")) + self.assertRaises(PackagingFileError, self.cmd.ensure_finalized) + + def test_checks_upload_dir(self): + self.cmd.upload_dir = self.prepare_sample_dir() + shutil.rmtree(os.path.join(self.cmd.upload_dir)) + self.assertRaises(PackagingOptionError, self.cmd.ensure_finalized) + + def test_show_response(self): + self.prepare_command() + self.cmd.show_response = True + self.cmd.run() + record = self.get_logs()[-1] + self.assertTrue(record, "should report the response") + self.assertIn(self.pypi.default_response_data, record) + +def test_suite(): + return unittest.makeSuite(UploadDocsTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_compiler.py b/Lib/packaging/tests/test_compiler.py new file mode 100644 index 0000000000..2c620cb513 --- /dev/null +++ b/Lib/packaging/tests/test_compiler.py @@ -0,0 +1,66 @@ +"""Tests for distutils.compiler.""" +import os + +from packaging.compiler import (get_default_compiler, customize_compiler, + gen_lib_options) +from packaging.tests import unittest, support + + +class FakeCompiler: + + name = 'fake' + description = 'Fake' + + def library_dir_option(self, dir): + return "-L" + dir + + def runtime_library_dir_option(self, dir): + return ["-cool", "-R" + dir] + + def find_library_file(self, dirs, lib, debug=False): + return 'found' + + def library_option(self, lib): + return "-l" + lib + + +class CompilerTestCase(support.EnvironRestorer, unittest.TestCase): + + restore_environ = ['AR', 'ARFLAGS'] + + @unittest.skipUnless(get_default_compiler() == 'unix', + 'irrelevant if default compiler is not unix') + def test_customize_compiler(self): + + os.environ['AR'] = 'my_ar' + os.environ['ARFLAGS'] = '-arflags' + + # make sure AR gets caught + class compiler: + name = 'unix' + + def set_executables(self, **kw): + self.exes = kw + + comp = compiler() + customize_compiler(comp) + self.assertEqual(comp.exes['archiver'], 'my_ar -arflags') + + def test_gen_lib_options(self): + compiler = FakeCompiler() + libdirs = ['lib1', 'lib2'] + runlibdirs = ['runlib1'] + libs = [os.path.join('dir', 'name'), 'name2'] + + opts = gen_lib_options(compiler, libdirs, runlibdirs, libs) + wanted = ['-Llib1', '-Llib2', '-cool', '-Rrunlib1', 'found', + '-lname2'] + self.assertEqual(opts, wanted) + + +def test_suite(): + return unittest.makeSuite(CompilerTestCase) + + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_config.py b/Lib/packaging/tests/test_config.py new file mode 100644 index 0000000000..8908c4fa4c --- /dev/null +++ b/Lib/packaging/tests/test_config.py @@ -0,0 +1,424 @@ +"""Tests for packaging.config.""" +import os +import sys +import logging +from io import StringIO + +from packaging import command +from packaging.dist import Distribution +from packaging.errors import PackagingFileError +from packaging.compiler import new_compiler, _COMPILERS +from packaging.command.sdist import sdist + +from packaging.tests import unittest, support + + +SETUP_CFG = """ +[metadata] +name = RestingParrot +version = 0.6.4 +author = Carl Meyer +author_email = carl@oddbird.net +maintainer = Éric Araujo +maintainer_email = merwok@netwok.org +summary = A sample project demonstrating packaging +description-file = %(description-file)s +keywords = packaging, sample project + +classifier = + Development Status :: 4 - Beta + Environment :: Console (Text Based) + Environment :: X11 Applications :: GTK; python_version < '3' + License :: OSI Approved :: MIT License + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 3 + +requires_python = >=2.4, <3.2 + +requires_dist = + PetShoppe + MichaelPalin (> 1.1) + pywin32; sys.platform == 'win32' + pysqlite2; python_version < '2.5' + inotify (0.0.1); sys.platform == 'linux2' + +requires_external = libxml2 + +provides_dist = packaging-sample-project (0.2) + unittest2-sample-project + +project_url = + Main repository, http://bitbucket.org/carljm/sample-distutils2-project + Fork in progress, http://bitbucket.org/Merwok/sample-distutils2-project + +[files] +packages_root = src + +packages = one + two + three + +modules = haven + +scripts = + script1.py + scripts/find-coconuts + bin/taunt + +package_data = + cheese = data/templates/* + +extra_files = %(extra-files)s + +# Replaces MANIFEST.in +sdist_extra = + include THANKS HACKING + recursive-include examples *.txt *.py + prune examples/sample?/build + +resources= + bm/ {b1,b2}.gif = {icon} + Cf*/ *.CFG = {config}/baBar/ + init_script = {script}/JunGle/ + +[global] +commands = + packaging.tests.test_config.FooBarBazTest + +compilers = + packaging.tests.test_config.DCompiler + +setup_hook = %(setup-hook)s + + + +[install_dist] +sub_commands = foo +""" + +# Can not be merged with SETUP_CFG else install_dist +# command will fail when trying to compile C sources +EXT_SETUP_CFG = """ +[files] +packages = one + two + +[extension=speed_coconuts] +name = one.speed_coconuts +sources = c_src/speed_coconuts.c +extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared +define_macros = HAVE_CAIRO HAVE_GTK2 +libraries = gecodeint gecodekernel -- sys.platform != 'win32' + GecodeInt GecodeKernel -- sys.platform == 'win32' + +[extension=fast_taunt] +name = three.fast_taunt +sources = cxx_src/utils_taunt.cxx + cxx_src/python_module.cxx +include_dirs = /usr/include/gecode + /usr/include/blitz +extra_compile_args = -fPIC -O2 + -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' + /DGECODE_VERSION='win32' -- sys.platform == 'win32' +language = cxx + +""" + + +class DCompiler: + name = 'd' + description = 'D Compiler' + + def __init__(self, *args): + pass + + +def hook(content): + content['metadata']['version'] += '.dev1' + + +class FooBarBazTest: + + def __init__(self, dist): + self.distribution = dist + + @classmethod + def get_command_name(cls): + return 'foo' + + def run(self): + self.distribution.foo_was_here = True + + def nothing(self): + pass + + def get_source_files(self): + return [] + + ensure_finalized = finalize_options = initialize_options = nothing + + +class ConfigTestCase(support.TempdirManager, + support.EnvironRestorer, + support.LoggingCatcher, + unittest.TestCase): + + restore_environ = ['PLAT'] + + def setUp(self): + super(ConfigTestCase, self).setUp() + self.addCleanup(setattr, sys, 'stdout', sys.stdout) + self.addCleanup(setattr, sys, 'stderr', sys.stderr) + sys.stdout = StringIO() + sys.stderr = StringIO() + + self.addCleanup(os.chdir, os.getcwd()) + tempdir = self.mkdtemp() + os.chdir(tempdir) + self.tempdir = tempdir + + def write_setup(self, kwargs=None): + opts = {'description-file': 'README', 'extra-files': '', + 'setup-hook': 'packaging.tests.test_config.hook'} + if kwargs: + opts.update(kwargs) + self.write_file('setup.cfg', SETUP_CFG % opts) + + def get_dist(self): + dist = Distribution() + dist.parse_config_files() + return dist + + def test_config(self): + self.write_setup() + self.write_file('README', 'yeah') + os.mkdir('bm') + self.write_file(('bm', 'b1.gif'), '') + self.write_file(('bm', 'b2.gif'), '') + os.mkdir('Cfg') + self.write_file(('Cfg', 'data.CFG'), '') + self.write_file('init_script', '') + + # try to load the metadata now + dist = self.get_dist() + + # check what was done + self.assertEqual(dist.metadata['Author'], 'Carl Meyer') + self.assertEqual(dist.metadata['Author-Email'], 'carl@oddbird.net') + + # the hook adds .dev1 + self.assertEqual(dist.metadata['Version'], '0.6.4.dev1') + + wanted = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console (Text Based)', + "Environment :: X11 Applications :: GTK; python_version < '3'", + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3'] + self.assertEqual(dist.metadata['Classifier'], wanted) + + wanted = ['packaging', 'sample project'] + self.assertEqual(dist.metadata['Keywords'], wanted) + + self.assertEqual(dist.metadata['Requires-Python'], '>=2.4, <3.2') + + wanted = ['PetShoppe', + 'MichaelPalin (> 1.1)', + "pywin32; sys.platform == 'win32'", + "pysqlite2; python_version < '2.5'", + "inotify (0.0.1); sys.platform == 'linux2'"] + + self.assertEqual(dist.metadata['Requires-Dist'], wanted) + urls = [('Main repository', + 'http://bitbucket.org/carljm/sample-distutils2-project'), + ('Fork in progress', + 'http://bitbucket.org/Merwok/sample-distutils2-project')] + self.assertEqual(dist.metadata['Project-Url'], urls) + + self.assertEqual(dist.packages, ['one', 'two', 'three']) + self.assertEqual(dist.py_modules, ['haven']) + self.assertEqual(dist.package_data, {'cheese': 'data/templates/*'}) + self.assertEqual( + {'bm/b1.gif': '{icon}/b1.gif', + 'bm/b2.gif': '{icon}/b2.gif', + 'Cfg/data.CFG': '{config}/baBar/data.CFG', + 'init_script': '{script}/JunGle/init_script'}, + dist.data_files) + + self.assertEqual(dist.package_dir, 'src') + + # Make sure we get the foo command loaded. We use a string comparison + # instead of assertIsInstance because the class is not the same when + # this test is run directly: foo is packaging.tests.test_config.Foo + # because get_command_class uses the full name, but a bare "Foo" in + # this file would be __main__.Foo when run as "python test_config.py". + # The name FooBarBazTest should be unique enough to prevent + # collisions. + self.assertEqual('FooBarBazTest', + dist.get_command_obj('foo').__class__.__name__) + + # did the README got loaded ? + self.assertEqual(dist.metadata['description'], 'yeah') + + # do we have the D Compiler enabled ? + self.assertIn('d', _COMPILERS) + d = new_compiler(compiler='d') + self.assertEqual(d.description, 'D Compiler') + + def test_multiple_description_file(self): + self.write_setup({'description-file': 'README CHANGES'}) + self.write_file('README', 'yeah') + self.write_file('CHANGES', 'changelog2') + dist = self.get_dist() + self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) + + def test_multiline_description_file(self): + self.write_setup({'description-file': 'README\n CHANGES'}) + self.write_file('README', 'yeah') + self.write_file('CHANGES', 'changelog') + dist = self.get_dist() + self.assertEqual(dist.metadata['description'], 'yeah\nchangelog') + self.assertEqual(dist.metadata.requires_files, ['README', 'CHANGES']) + + def test_parse_extensions_in_config(self): + self.write_file('setup.cfg', EXT_SETUP_CFG) + dist = self.get_dist() + + ext_modules = dict((mod.name, mod) for mod in dist.ext_modules) + self.assertEqual(len(ext_modules), 2) + ext = ext_modules.get('one.speed_coconuts') + self.assertEqual(ext.sources, ['c_src/speed_coconuts.c']) + self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2']) + libs = ['gecodeint', 'gecodekernel'] + if sys.platform == 'win32': + libs = ['GecodeInt', 'GecodeKernel'] + self.assertEqual(ext.libraries, libs) + self.assertEqual(ext.extra_link_args, + ['`gcc -print-file-name=libgcc.a`', '-shared']) + + ext = ext_modules.get('three.fast_taunt') + self.assertEqual(ext.sources, + ['cxx_src/utils_taunt.cxx', 'cxx_src/python_module.cxx']) + self.assertEqual(ext.include_dirs, + ['/usr/include/gecode', '/usr/include/blitz']) + cargs = ['-fPIC', '-O2'] + if sys.platform == 'win32': + cargs.append("/DGECODE_VERSION='win32'") + else: + cargs.append('-DGECODE_VERSION=$(./gecode_version)') + self.assertEqual(ext.extra_compile_args, cargs) + self.assertEqual(ext.language, 'cxx') + + def test_missing_setuphook_warns(self): + self.write_setup({'setup-hook': 'this.does._not.exist'}) + self.write_file('README', 'yeah') + dist = self.get_dist() + logs = self.get_logs(logging.WARNING) + self.assertEqual(1, len(logs)) + self.assertIn('could not import setup_hook', logs[0]) + + def test_metadata_requires_description_files_missing(self): + self.write_setup({'description-file': 'README\n README2'}) + self.write_file('README', 'yeah') + self.write_file('README2', 'yeah') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + dist = self.get_dist() + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + self.assertRaises(PackagingFileError, cmd.make_distribution) + + def test_metadata_requires_description_files(self): + # Create the following file structure: + # README + # README2 + # script1.py + # scripts/ + # find-coconuts + # bin/ + # taunt + # src/ + # haven.py + # one/__init__.py + # two/__init__.py + # three/__init__.py + + self.write_setup({'description-file': 'README\n README2', + 'extra-files': '\n README3'}) + self.write_file('README', 'yeah 1') + self.write_file('README2', 'yeah 2') + self.write_file('README3', 'yeah 3') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + dist = self.get_dist() + self.assertIn('yeah 1\nyeah 2', dist.metadata['description']) + + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + self.assertRaises(PackagingFileError, cmd.make_distribution) + + self.write_setup({'description-file': 'README\n README2', + 'extra-files': '\n README2\n README'}) + dist = self.get_dist() + cmd = sdist(dist) + cmd.finalize_options() + cmd.get_file_list() + cmd.make_distribution() + with open('MANIFEST') as fp: + self.assertIn('README\nREADME2\n', fp.read()) + + def test_sub_commands(self): + self.write_setup() + self.write_file('README', 'yeah') + os.mkdir('src') + self.write_file(('src', 'haven.py'), '#') + self.write_file('script1.py', '#') + os.mkdir('scripts') + self.write_file(('scripts', 'find-coconuts'), '#') + os.mkdir('bin') + self.write_file(('bin', 'taunt'), '#') + + for pkg in ('one', 'two', 'three'): + pkg = os.path.join('src', pkg) + os.mkdir(pkg) + self.write_file((pkg, '__init__.py'), '#') + + # try to run the install command to see if foo is called + dist = self.get_dist() + self.assertIn('foo', command.get_command_names()) + self.assertEqual('FooBarBazTest', + dist.get_command_obj('foo').__class__.__name__) + + +def test_suite(): + return unittest.makeSuite(ConfigTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_create.py b/Lib/packaging/tests/test_create.py new file mode 100644 index 0000000000..99ab0633d3 --- /dev/null +++ b/Lib/packaging/tests/test_create.py @@ -0,0 +1,235 @@ +"""Tests for packaging.create.""" +import io +import os +import sys +import sysconfig +from textwrap import dedent +from packaging.create import MainProgram, ask_yn, ask, main + +from packaging.tests import support, unittest + + +class CreateTestCase(support.TempdirManager, + support.EnvironRestorer, + unittest.TestCase): + + restore_environ = ['PLAT'] + + def setUp(self): + super(CreateTestCase, self).setUp() + self._stdin = sys.stdin # TODO use Inputs + self._stdout = sys.stdout + sys.stdin = io.StringIO() + sys.stdout = io.StringIO() + self._cwd = os.getcwd() + self.wdir = self.mkdtemp() + os.chdir(self.wdir) + # patch sysconfig + self._old_get_paths = sysconfig.get_paths + sysconfig.get_paths = lambda *args, **kwargs: { + 'man': sys.prefix + '/share/man', + 'doc': sys.prefix + '/share/doc/pyxfoil', } + + def tearDown(self): + super(CreateTestCase, self).tearDown() + sys.stdin = self._stdin + sys.stdout = self._stdout + os.chdir(self._cwd) + sysconfig.get_paths = self._old_get_paths + + def test_ask_yn(self): + sys.stdin.write('y\n') + sys.stdin.seek(0) + self.assertEqual('y', ask_yn('is this a test')) + + def test_ask(self): + sys.stdin.write('a\n') + sys.stdin.write('b\n') + sys.stdin.seek(0) + self.assertEqual('a', ask('is this a test')) + self.assertEqual('b', ask(str(list(range(0, 70))), default='c', + lengthy=True)) + + def test_set_multi(self): + mainprogram = MainProgram() + sys.stdin.write('aaaaa\n') + sys.stdin.seek(0) + mainprogram.data['author'] = [] + mainprogram._set_multi('_set_multi test', 'author') + self.assertEqual(['aaaaa'], mainprogram.data['author']) + + def test_find_files(self): + # making sure we scan a project dir correctly + mainprogram = MainProgram() + + # building the structure + tempdir = self.wdir + dirs = ['pkg1', 'data', 'pkg2', 'pkg2/sub'] + files = ['README', 'setup.cfg', 'foo.py', + 'pkg1/__init__.py', 'pkg1/bar.py', + 'data/data1', 'pkg2/__init__.py', + 'pkg2/sub/__init__.py'] + + for dir_ in dirs: + os.mkdir(os.path.join(tempdir, dir_)) + + for file_ in files: + path = os.path.join(tempdir, file_) + self.write_file(path, 'xxx') + + mainprogram._find_files() + mainprogram.data['packages'].sort() + + # do we have what we want? + self.assertEqual(mainprogram.data['packages'], + ['pkg1', 'pkg2', 'pkg2.sub']) + self.assertEqual(mainprogram.data['modules'], ['foo']) + data_fn = os.path.join('data', 'data1') + self.assertEqual(set(mainprogram.data['extra_files']), + set(['setup.cfg', 'README', data_fn])) + + def test_convert_setup_py_to_cfg(self): + self.write_file((self.wdir, 'setup.py'), + dedent(""" + # -*- coding: utf-8 -*- + from distutils.core import setup + + long_description = '''My super Death-scription + barbar is now on the public domain, + ho, baby !''' + + setup(name='pyxfoil', + version='0.2', + description='Python bindings for the Xfoil engine', + long_description=long_description, + maintainer='André Espaze', + maintainer_email='andre.espaze@logilab.fr', + url='http://www.python-science.org/project/pyxfoil', + license='GPLv2', + packages=['pyxfoil', 'babar', 'me'], + data_files=[ + ('share/doc/pyxfoil', ['README.rst']), + ('share/man', ['pyxfoil.1']), + ], + py_modules=['my_lib', 'mymodule'], + package_dir={ + 'babar': '', + 'me': 'Martinique/Lamentin', + }, + package_data={ + 'babar': ['Pom', 'Flora', 'Alexander'], + 'me': ['dady', 'mumy', 'sys', 'bro'], + '': ['setup.py', 'README'], + 'pyxfoil': ['fengine.so'], + }, + scripts=['my_script', 'bin/run'], + ) + """)) + sys.stdin.write('y\n') + sys.stdin.seek(0) + main() + + with open(os.path.join(self.wdir, 'setup.cfg')) as fp: + lines = set(line.rstrip() for line in fp) + + # FIXME don't use sets + self.assertEqual(lines, set(['', + '[metadata]', + 'version = 0.2', + 'name = pyxfoil', + 'maintainer = André Espaze', + 'description = My super Death-scription', + ' |barbar is now on the public domain,', + ' |ho, baby !', + 'maintainer_email = andre.espaze@logilab.fr', + 'home_page = http://www.python-science.org/project/pyxfoil', + 'download_url = UNKNOWN', + 'summary = Python bindings for the Xfoil engine', + '[files]', + 'modules = my_lib', + ' mymodule', + 'packages = pyxfoil', + ' babar', + ' me', + 'extra_files = Martinique/Lamentin/dady', + ' Martinique/Lamentin/mumy', + ' Martinique/Lamentin/sys', + ' Martinique/Lamentin/bro', + ' Pom', + ' Flora', + ' Alexander', + ' setup.py', + ' README', + ' pyxfoil/fengine.so', + 'scripts = my_script', + ' bin/run', + 'resources =', + ' README.rst = {doc}', + ' pyxfoil.1 = {man}', + ])) + + def test_convert_setup_py_to_cfg_with_description_in_readme(self): + self.write_file((self.wdir, 'setup.py'), + dedent(""" + # -*- coding: utf-8 -*- + from distutils.core import setup + fp = open('README.txt') + try: + long_description = fp.read() + finally: + fp.close() + + setup(name='pyxfoil', + version='0.2', + description='Python bindings for the Xfoil engine', + long_description=long_description, + maintainer='André Espaze', + maintainer_email='andre.espaze@logilab.fr', + url='http://www.python-science.org/project/pyxfoil', + license='GPLv2', + packages=['pyxfoil'], + package_data={'pyxfoil': ['fengine.so', 'babar.so']}, + data_files=[ + ('share/doc/pyxfoil', ['README.rst']), + ('share/man', ['pyxfoil.1']), + ], + ) + """)) + self.write_file((self.wdir, 'README.txt'), + dedent(''' +My super Death-scription +barbar is now in the public domain, +ho, baby! + ''')) + sys.stdin.write('y\n') + sys.stdin.seek(0) + # FIXME Out of memory error. + main() + with open(os.path.join(self.wdir, 'setup.cfg')) as fp: + lines = set(line.rstrip() for line in fp) + + self.assertEqual(lines, set(['', + '[metadata]', + 'version = 0.2', + 'name = pyxfoil', + 'maintainer = André Espaze', + 'maintainer_email = andre.espaze@logilab.fr', + 'home_page = http://www.python-science.org/project/pyxfoil', + 'download_url = UNKNOWN', + 'summary = Python bindings for the Xfoil engine', + 'description-file = README.txt', + '[files]', + 'packages = pyxfoil', + 'extra_files = pyxfoil/fengine.so', + ' pyxfoil/babar.so', + 'resources =', + ' README.rst = {doc}', + ' pyxfoil.1 = {man}', + ])) + + +def test_suite(): + return unittest.makeSuite(CreateTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_cygwinccompiler.py b/Lib/packaging/tests/test_cygwinccompiler.py new file mode 100644 index 0000000000..17c43cd28a --- /dev/null +++ b/Lib/packaging/tests/test_cygwinccompiler.py @@ -0,0 +1,88 @@ +"""Tests for packaging.cygwinccompiler.""" +import os +import sys +import sysconfig +from packaging.compiler.cygwinccompiler import ( + check_config_h, get_msvcr, + CONFIG_H_OK, CONFIG_H_NOTOK, CONFIG_H_UNCERTAIN) + +from packaging.tests import unittest, support + + +class CygwinCCompilerTestCase(support.TempdirManager, + unittest.TestCase): + + def setUp(self): + super(CygwinCCompilerTestCase, self).setUp() + self.version = sys.version + self.python_h = os.path.join(self.mkdtemp(), 'python.h') + self.old_get_config_h_filename = sysconfig.get_config_h_filename + sysconfig.get_config_h_filename = self._get_config_h_filename + + def tearDown(self): + sys.version = self.version + sysconfig.get_config_h_filename = self.old_get_config_h_filename + super(CygwinCCompilerTestCase, self).tearDown() + + def _get_config_h_filename(self): + return self.python_h + + def test_check_config_h(self): + # check_config_h looks for "GCC" in sys.version first + # returns CONFIG_H_OK if found + sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) \n[GCC ' + '4.0.1 (Apple Computer, Inc. build 5370)]') + + self.assertEqual(check_config_h()[0], CONFIG_H_OK) + + # then it tries to see if it can find "__GNUC__" in pyconfig.h + sys.version = 'something without the *CC word' + + # if the file doesn't exist it returns CONFIG_H_UNCERTAIN + self.assertEqual(check_config_h()[0], CONFIG_H_UNCERTAIN) + + # if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK + self.write_file(self.python_h, 'xxx') + self.assertEqual(check_config_h()[0], CONFIG_H_NOTOK) + + # and CONFIG_H_OK if __GNUC__ is found + self.write_file(self.python_h, 'xxx __GNUC__ xxx') + self.assertEqual(check_config_h()[0], CONFIG_H_OK) + + def test_get_msvcr(self): + # none + sys.version = ('2.6.1 (r261:67515, Dec 6 2008, 16:42:21) ' + '\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]') + self.assertEqual(get_msvcr(), None) + + # MSVC 7.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1300 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr70']) + + # MSVC 7.1 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1310 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr71']) + + # VS2005 / MSVC 8.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1400 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr80']) + + # VS2008 / MSVC 9.0 + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1500 32 bits (Intel)]') + self.assertEqual(get_msvcr(), ['msvcr90']) + + # unknown + sys.version = ('2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' + '[MSC v.1999 32 bits (Intel)]') + self.assertRaises(ValueError, get_msvcr) + + +def test_suite(): + return unittest.makeSuite(CygwinCCompilerTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_database.py b/Lib/packaging/tests/test_database.py new file mode 100644 index 0000000000..c8d941527e --- /dev/null +++ b/Lib/packaging/tests/test_database.py @@ -0,0 +1,506 @@ +import os +import io +import csv +import imp +import sys +import shutil +import zipfile +import tempfile +from os.path import relpath # separate import for backport concerns +from hashlib import md5 + +from packaging.errors import PackagingError +from packaging.metadata import Metadata +from packaging.tests import unittest, run_unittest, support, TESTFN + +from packaging.database import ( + Distribution, EggInfoDistribution, get_distribution, get_distributions, + provides_distribution, obsoletes_distribution, get_file_users, + enable_cache, disable_cache, distinfo_dirname, _yield_distributions) + +# TODO Add a test for getting a distribution provided by another distribution +# TODO Add a test for absolute pathed RECORD items (e.g. /etc/myapp/config.ini) +# TODO Add tests from the former pep376 project (zipped site-packages, etc.) + + +def get_hexdigest(filename): + with open(filename, 'rb') as file: + checksum = md5(file.read()) + return checksum.hexdigest() + + +def record_pieces(file): + path = relpath(file, sys.prefix) + digest = get_hexdigest(file) + size = os.path.getsize(file) + return [path, digest, size] + + +class CommonDistributionTests: + """Mixin used to test the interface common to both Distribution classes. + + Derived classes define cls, sample_dist, dirs and records. These + attributes are used in test methods. See source code for details. + """ + + def setUp(self): + super(CommonDistributionTests, self).setUp() + self.addCleanup(enable_cache) + disable_cache() + self.fake_dists_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'fake_dists')) + + def test_instantiation(self): + # check that useful attributes are here + name, version, distdir = self.sample_dist + here = os.path.abspath(os.path.dirname(__file__)) + dist_path = os.path.join(here, 'fake_dists', distdir) + + dist = self.dist = self.cls(dist_path) + self.assertEqual(dist.path, dist_path) + self.assertEqual(dist.name, name) + self.assertEqual(dist.metadata['Name'], name) + self.assertIsInstance(dist.metadata, Metadata) + self.assertEqual(dist.version, version) + self.assertEqual(dist.metadata['Version'], version) + + def test_repr(self): + dist = self.cls(self.dirs[0]) + # just check that the class name is in the repr + self.assertIn(self.cls.__name__, repr(dist)) + + def test_comparison(self): + # tests for __eq__ and __hash__ + dist = self.cls(self.dirs[0]) + dist2 = self.cls(self.dirs[0]) + dist3 = self.cls(self.dirs[1]) + self.assertIn(dist, {dist: True}) + self.assertEqual(dist, dist) + + self.assertIsNot(dist, dist2) + self.assertEqual(dist, dist2) + self.assertNotEqual(dist, dist3) + self.assertNotEqual(dist, ()) + + def test_list_installed_files(self): + for dir_ in self.dirs: + dist = self.cls(dir_) + for path, md5_, size in dist.list_installed_files(): + record_data = self.records[dist.path] + self.assertIn(path, record_data) + self.assertEqual(md5_, record_data[path][0]) + self.assertEqual(size, record_data[path][1]) + + +class TestDistribution(CommonDistributionTests, unittest.TestCase): + + cls = Distribution + sample_dist = 'choxie', '2.0.0.9', 'choxie-2.0.0.9.dist-info' + + def setUp(self): + super(TestDistribution, self).setUp() + self.dirs = [os.path.join(self.fake_dists_path, f) + for f in os.listdir(self.fake_dists_path) + if f.endswith('.dist-info')] + + self.records = {} + for distinfo_dir in self.dirs: + record_file = os.path.join(distinfo_dir, 'RECORD') + with open(record_file, 'w') as file: + record_writer = csv.writer( + file, delimiter=',', quoting=csv.QUOTE_NONE) + + dist_location = distinfo_dir.replace('.dist-info', '') + + for path, dirs, files in os.walk(dist_location): + for f in files: + record_writer.writerow(record_pieces( + os.path.join(path, f))) + for file in ('INSTALLER', 'METADATA', 'REQUESTED'): + record_writer.writerow(record_pieces( + os.path.join(distinfo_dir, file))) + record_writer.writerow([relpath(record_file, sys.prefix)]) + + with open(record_file) as file: + record_reader = csv.reader(file) + record_data = {} + for row in record_reader: + path, md5_, size = (row[:] + + [None for i in range(len(row), 3)]) + record_data[path] = md5_, size + self.records[distinfo_dir] = record_data + + def tearDown(self): + for distinfo_dir in self.dirs: + record_file = os.path.join(distinfo_dir, 'RECORD') + open(record_file, 'w').close() + super(TestDistribution, self).tearDown() + + def test_instantiation(self): + super(TestDistribution, self).test_instantiation() + self.assertIsInstance(self.dist.requested, bool) + + def test_uses(self): + # Test to determine if a distribution uses a specified file. + # Criteria to test against + distinfo_name = 'grammar-1.0a4' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + true_path = [self.fake_dists_path, distinfo_name, + 'grammar', 'utils.py'] + true_path = relpath(os.path.join(*true_path), sys.prefix) + false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff', + '__init__.py'] + false_path = relpath(os.path.join(*false_path), sys.prefix) + + # Test if the distribution uses the file in question + dist = Distribution(distinfo_dir) + self.assertTrue(dist.uses(true_path)) + self.assertFalse(dist.uses(false_path)) + + def test_get_distinfo_file(self): + # Test the retrieval of dist-info file objects. + distinfo_name = 'choxie-2.0.0.9' + other_distinfo_name = 'grammar-1.0a4' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + # Test for known good file matches + distinfo_files = [ + # Relative paths + 'INSTALLER', 'METADATA', + # Absolute paths + os.path.join(distinfo_dir, 'RECORD'), + os.path.join(distinfo_dir, 'REQUESTED'), + ] + + for distfile in distinfo_files: + with dist.get_distinfo_file(distfile) as value: + self.assertIsInstance(value, io.TextIOWrapper) + # Is it the correct file? + self.assertEqual(value.name, + os.path.join(distinfo_dir, distfile)) + + # Test an absolute path that is part of another distributions dist-info + other_distinfo_file = os.path.join( + self.fake_dists_path, other_distinfo_name + '.dist-info', + 'REQUESTED') + self.assertRaises(PackagingError, dist.get_distinfo_file, + other_distinfo_file) + # Test for a file that should not exist + self.assertRaises(PackagingError, dist.get_distinfo_file, + 'MAGICFILE') + + def test_list_distinfo_files(self): + # Test for the iteration of RECORD path entries. + distinfo_name = 'towel_stuff-0.1' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + # Test for the iteration of the raw path + distinfo_record_paths = self.records[distinfo_dir].keys() + found = dist.list_distinfo_files() + self.assertEqual(sorted(found), sorted(distinfo_record_paths)) + # Test for the iteration of local absolute paths + distinfo_record_paths = [os.path.join(sys.prefix, path) + for path in self.records[distinfo_dir]] + found = dist.list_distinfo_files(local=True) + self.assertEqual(sorted(found), sorted(distinfo_record_paths)) + + def test_get_resources_path(self): + distinfo_name = 'babar-0.1' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + resource_path = dist.get_resource_path('babar.png') + self.assertEqual(resource_path, 'babar.png') + self.assertRaises(KeyError, dist.get_resource_path, 'notexist') + + +class TestEggInfoDistribution(CommonDistributionTests, + support.LoggingCatcher, + unittest.TestCase): + + cls = EggInfoDistribution + sample_dist = 'bacon', '0.1', 'bacon-0.1.egg-info' + + def setUp(self): + super(TestEggInfoDistribution, self).setUp() + + self.dirs = [os.path.join(self.fake_dists_path, f) + for f in os.listdir(self.fake_dists_path) + if f.endswith('.egg') or f.endswith('.egg-info')] + + self.records = {} + + @unittest.skip('not implemented yet') + def test_list_installed_files(self): + # EggInfoDistribution defines list_installed_files but there is no + # test for it yet; someone with setuptools expertise needs to add a + # file with the list of installed files for one of the egg fake dists + # and write the support code to populate self.records (and then delete + # this method) + pass + + +class TestDatabase(support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(TestDatabase, self).setUp() + disable_cache() + # Setup the path environment with our fake distributions + current_path = os.path.abspath(os.path.dirname(__file__)) + self.sys_path = sys.path[:] + self.fake_dists_path = os.path.join(current_path, 'fake_dists') + sys.path.insert(0, self.fake_dists_path) + + def tearDown(self): + sys.path[:] = self.sys_path + enable_cache() + super(TestDatabase, self).tearDown() + + def test_distinfo_dirname(self): + # Given a name and a version, we expect the distinfo_dirname function + # to return a standard distribution information directory name. + + items = [ + # (name, version, standard_dirname) + # Test for a very simple single word name and decimal version + # number + ('docutils', '0.5', 'docutils-0.5.dist-info'), + # Test for another except this time with a '-' in the name, which + # needs to be transformed during the name lookup + ('python-ldap', '2.5', 'python_ldap-2.5.dist-info'), + # Test for both '-' in the name and a funky version number + ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'), + ] + + # Loop through the items to validate the results + for name, version, standard_dirname in items: + dirname = distinfo_dirname(name, version) + self.assertEqual(dirname, standard_dirname) + + def test_get_distributions(self): + # Lookup all distributions found in the ``sys.path``. + # This test could potentially pick up other installed distributions + fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'), + ('towel-stuff', '0.1'), ('babar', '0.1')] + found_dists = [] + + # Verify the fake dists have been found. + dists = [dist for dist in get_distributions()] + for dist in dists: + self.assertIsInstance(dist, Distribution) + if (dist.name in dict(fake_dists) and + dist.path.startswith(self.fake_dists_path)): + found_dists.append((dist.name, dist.metadata['version'], )) + else: + # check that it doesn't find anything more than this + self.assertFalse(dist.path.startswith(self.fake_dists_path)) + # otherwise we don't care what other distributions are found + + # Finally, test that we found all that we were looking for + self.assertEqual(sorted(found_dists), sorted(fake_dists)) + + # Now, test if the egg-info distributions are found correctly as well + fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'), + ('coconuts-aster', '10.3'), + ('banana', '0.4'), ('strawberry', '0.6'), + ('truffles', '5.0'), ('nut', 'funkyversion')] + found_dists = [] + + dists = [dist for dist in get_distributions(use_egg_info=True)] + for dist in dists: + self.assertIsInstance(dist, (Distribution, EggInfoDistribution)) + if (dist.name in dict(fake_dists) and + dist.path.startswith(self.fake_dists_path)): + found_dists.append((dist.name, dist.metadata['version'])) + else: + self.assertFalse(dist.path.startswith(self.fake_dists_path)) + + self.assertEqual(sorted(fake_dists), sorted(found_dists)) + + def test_get_distribution(self): + # Test for looking up a distribution by name. + # Test the lookup of the towel-stuff distribution + name = 'towel-stuff' # Note: This is different from the directory name + + # Lookup the distribution + dist = get_distribution(name) + self.assertIsInstance(dist, Distribution) + self.assertEqual(dist.name, name) + + # Verify that an unknown distribution returns None + self.assertIsNone(get_distribution('bogus')) + + # Verify partial name matching doesn't work + self.assertIsNone(get_distribution('towel')) + + # Verify that it does not find egg-info distributions, when not + # instructed to + self.assertIsNone(get_distribution('bacon')) + self.assertIsNone(get_distribution('cheese')) + self.assertIsNone(get_distribution('strawberry')) + self.assertIsNone(get_distribution('banana')) + + # Now check that it works well in both situations, when egg-info + # is a file and directory respectively. + dist = get_distribution('cheese', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'cheese') + + dist = get_distribution('bacon', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'bacon') + + dist = get_distribution('banana', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'banana') + + dist = get_distribution('strawberry', use_egg_info=True) + self.assertIsInstance(dist, EggInfoDistribution) + self.assertEqual(dist.name, 'strawberry') + + def test_get_file_users(self): + # Test the iteration of distributions that use a file. + name = 'towel_stuff-0.1' + path = os.path.join(self.fake_dists_path, name, + 'towel_stuff', '__init__.py') + for dist in get_file_users(path): + self.assertIsInstance(dist, Distribution) + self.assertEqual(dist.name, name) + + def test_provides(self): + # Test for looking up distributions by what they provide + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + l = [dist.name for dist in provides_distribution('truffles')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '1.0')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in provides_distribution('truffles', '1.0', + use_egg_info=True)] + checkLists(l, ['choxie', 'cheese']) + + l = [dist.name for dist in provides_distribution('truffles', '1.1.2')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '1.1')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', + '!=1.1,<=2.0')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in provides_distribution('truffles', + '!=1.1,<=2.0', + use_egg_info=True)] + checkLists(l, ['choxie', 'bacon', 'cheese']) + + l = [dist.name for dist in provides_distribution('truffles', '>1.0')] + checkLists(l, ['towel-stuff']) + + l = [dist.name for dist in provides_distribution('truffles', '>1.5')] + checkLists(l, []) + + l = [dist.name for dist in provides_distribution('truffles', '>1.5', + use_egg_info=True)] + checkLists(l, ['bacon']) + + l = [dist.name for dist in provides_distribution('truffles', '>=1.0')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in provides_distribution('strawberry', '0.6', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('strawberry', '>=0.5', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('strawberry', '>0.6', + use_egg_info=True)] + checkLists(l, []) + + l = [dist.name for dist in provides_distribution('banana', '0.4', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('banana', '>=0.3', + use_egg_info=True)] + checkLists(l, ['coconuts-aster']) + + l = [dist.name for dist in provides_distribution('banana', '!=0.4', + use_egg_info=True)] + checkLists(l, []) + + def test_obsoletes(self): + # Test looking for distributions based on what they obsolete + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')] + checkLists(l, []) + + l = [dist.name for dist in obsoletes_distribution('truffles', '1.0', + use_egg_info=True)] + checkLists(l, ['cheese', 'bacon']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.8')] + checkLists(l, ['choxie']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.8', + use_egg_info=True)] + checkLists(l, ['choxie', 'cheese']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.9.6')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in obsoletes_distribution('truffles', + '0.5.2.3')] + checkLists(l, ['choxie', 'towel-stuff']) + + l = [dist.name for dist in obsoletes_distribution('truffles', '0.2')] + checkLists(l, ['towel-stuff']) + + def test_yield_distribution(self): + # tests the internal function _yield_distributions + checkLists = lambda x, y: self.assertEqual(sorted(x), sorted(y)) + + eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'), + ('truffles', '5.0'), ('cheese', '2.0.2'), + ('coconuts-aster', '10.3'), ('nut', 'funkyversion')] + dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'), + ('towel-stuff', '0.1'), ('babar', '0.1')] + + checkLists([], _yield_distributions(False, False)) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(False, True) + if dist.path.startswith(self.fake_dists_path)] + checkLists(eggs, found) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(True, False) + if dist.path.startswith(self.fake_dists_path)] + checkLists(dists, found) + + found = [(dist.name, dist.metadata['Version']) + for dist in _yield_distributions(True, True) + if dist.path.startswith(self.fake_dists_path)] + checkLists(dists + eggs, found) + + +def test_suite(): + suite = unittest.TestSuite() + load = unittest.defaultTestLoader.loadTestsFromTestCase + suite.addTest(load(TestDistribution)) + suite.addTest(load(TestEggInfoDistribution)) + suite.addTest(load(TestDatabase)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest='test_suite') diff --git a/Lib/packaging/tests/test_depgraph.py b/Lib/packaging/tests/test_depgraph.py new file mode 100644 index 0000000000..9271a7ba68 --- /dev/null +++ b/Lib/packaging/tests/test_depgraph.py @@ -0,0 +1,301 @@ +"""Tests for packaging.depgraph """ +import io +import os +import re +import sys +import packaging.database +from packaging import depgraph + +from packaging.tests import unittest, support + + +class DepGraphTestCase(support.LoggingCatcher, + unittest.TestCase): + + DISTROS_DIST = ('choxie', 'grammar', 'towel-stuff') + DISTROS_EGG = ('bacon', 'banana', 'strawberry', 'cheese') + BAD_EGGS = ('nut',) + + EDGE = re.compile( + r'"(?P.*)" -> "(?P.*)" \[label="(?P