From 859aad6a36262383b98ddd45fe3253a882b87ce8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C3=89ric=20Araujo?= Date: Sun, 24 Jun 2012 00:07:41 -0400 Subject: [PATCH] Remove packaging from the standard library. Distutils2 will live on on PyPI and be included in the stdlib when it is ready. See discussion starting at http://mail.python.org/pipermail/python-dev/2012-June/120430.html --- Doc/contents.rst | 2 +- Doc/distutils/index.rst | 12 +- Doc/install/index.rst | 56 - Doc/install/install.rst | 1119 ------------- Doc/install/pysetup-config.rst | 44 - Doc/install/pysetup-servers.rst | 61 - Doc/install/pysetup.rst | 164 -- Doc/library/distutils.rst | 12 - Doc/library/packaging-misc.rst | 27 - Doc/library/packaging.command.rst | 111 -- Doc/library/packaging.compiler.rst | 681 -------- Doc/library/packaging.database.rst | 345 ---- Doc/library/packaging.depgraph.rst | 199 --- Doc/library/packaging.dist.rst | 108 -- Doc/library/packaging.fancy_getopt.rst | 75 - Doc/library/packaging.install.rst | 112 -- Doc/library/packaging.metadata.rst | 122 -- Doc/library/packaging.pypi.dist.rst | 114 -- Doc/library/packaging.pypi.rst | 74 - Doc/library/packaging.pypi.simple.rst | 218 --- Doc/library/packaging.pypi.xmlrpc.rst | 143 -- Doc/library/packaging.rst | 75 - Doc/library/packaging.tests.pypi_server.rst | 105 -- Doc/library/packaging.util.rst | 155 -- Doc/library/packaging.version.rst | 104 -- Doc/library/python.rst | 1 - Doc/library/site.rst | 4 +- Doc/library/venv.rst | 3 +- Doc/packaging/builtdist.rst | 302 ---- Doc/packaging/commandhooks.rst | 47 - Doc/packaging/commandref.rst | 374 ----- Doc/packaging/configfile.rst | 125 -- Doc/packaging/examples.rst | 334 ---- Doc/packaging/extending.rst | 95 -- Doc/packaging/index.rst | 45 - Doc/packaging/introduction.rst | 193 --- Doc/packaging/packageindex.rst | 104 -- Doc/packaging/setupcfg.rst | 890 ---------- Doc/packaging/setupscript.rst | 693 -------- Doc/packaging/sourcedist.rst | 266 --- Doc/packaging/tutorial.rst | 112 -- Doc/packaging/uploading.rst | 80 - Doc/tools/sphinxext/indexcontent.html | 8 +- Doc/tools/sphinxext/susp-ignored.csv | 22 - Doc/using/cmdline.rst | 4 +- Doc/using/scripts.rst | 3 +- Doc/whatsnew/3.3.rst | 47 +- Lib/packaging/__init__.py | 17 - Lib/packaging/_trove.py | 571 ------- Lib/packaging/command/__init__.py | 53 - Lib/packaging/command/bdist.py | 141 -- Lib/packaging/command/bdist_dumb.py | 139 -- Lib/packaging/command/bdist_msi.py | 743 --------- Lib/packaging/command/bdist_wininst.py | 345 ---- Lib/packaging/command/build.py | 151 -- Lib/packaging/command/build_clib.py | 197 --- Lib/packaging/command/build_ext.py | 644 ------- Lib/packaging/command/build_py.py | 392 ----- Lib/packaging/command/build_scripts.py | 154 -- Lib/packaging/command/check.py | 88 - Lib/packaging/command/clean.py | 76 - Lib/packaging/command/cmd.py | 461 ----- Lib/packaging/command/command_template | 35 - Lib/packaging/command/config.py | 349 ---- Lib/packaging/command/install_data.py | 79 - Lib/packaging/command/install_dist.py | 605 ------- Lib/packaging/command/install_distinfo.py | 143 -- Lib/packaging/command/install_headers.py | 43 - Lib/packaging/command/install_lib.py | 188 --- Lib/packaging/command/install_scripts.py | 59 - Lib/packaging/command/register.py | 263 --- Lib/packaging/command/sdist.py | 347 ---- Lib/packaging/command/test.py | 80 - Lib/packaging/command/upload.py | 168 -- Lib/packaging/command/upload_docs.py | 131 -- Lib/packaging/command/wininst-10.0-amd64.exe | Bin 222208 -> 0 bytes Lib/packaging/command/wininst-10.0.exe | Bin 190464 -> 0 bytes Lib/packaging/command/wininst-6.0.exe | Bin 61440 -> 0 bytes Lib/packaging/command/wininst-7.1.exe | Bin 65536 -> 0 bytes Lib/packaging/command/wininst-8.0.exe | Bin 61440 -> 0 bytes Lib/packaging/command/wininst-9.0-amd64.exe | Bin 223744 -> 0 bytes Lib/packaging/command/wininst-9.0.exe | Bin 196096 -> 0 bytes Lib/packaging/compat.py | 50 - Lib/packaging/compiler/__init__.py | 274 --- Lib/packaging/compiler/bcppcompiler.py | 355 ---- Lib/packaging/compiler/ccompiler.py | 863 ---------- Lib/packaging/compiler/cygwinccompiler.py | 355 ---- Lib/packaging/compiler/extension.py | 121 -- Lib/packaging/compiler/msvc9compiler.py | 721 -------- Lib/packaging/compiler/msvccompiler.py | 635 ------- Lib/packaging/compiler/unixccompiler.py | 339 ---- Lib/packaging/config.py | 391 ----- Lib/packaging/create.py | 682 -------- Lib/packaging/database.py | 651 -------- Lib/packaging/depgraph.py | 270 --- Lib/packaging/dist.py | 769 --------- Lib/packaging/errors.py | 138 -- Lib/packaging/fancy_getopt.py | 388 ----- Lib/packaging/install.py | 529 ------ Lib/packaging/manifest.py | 381 ----- Lib/packaging/markers.py | 189 --- Lib/packaging/metadata.py | 570 ------- Lib/packaging/pypi/__init__.py | 9 - Lib/packaging/pypi/base.py | 48 - Lib/packaging/pypi/dist.py | 544 ------ Lib/packaging/pypi/errors.py | 39 - Lib/packaging/pypi/mirrors.py | 52 - Lib/packaging/pypi/simple.py | 462 ----- Lib/packaging/pypi/wrapper.py | 99 -- Lib/packaging/pypi/xmlrpc.py | 200 --- Lib/packaging/run.py | 663 -------- 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 | 28 - Lib/packaging/tests/__main__.py | 24 - .../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 1402 -> 0 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_echo.py | 16 - Lib/packaging/tests/fixer/fix_echo2.py | 16 - Lib/packaging/tests/pypi_server.py | 449 ----- Lib/packaging/tests/pypi_test_server.py | 59 - .../source/f/foobar/foobar-0.1.tar.gz | Bin 110 -> 0 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 | 400 ----- Lib/packaging/tests/test_ccompiler.py | 15 - Lib/packaging/tests/test_command_bdist.py | 61 - .../tests/test_command_bdist_dumb.py | 91 - Lib/packaging/tests/test_command_bdist_msi.py | 25 - .../tests/test_command_bdist_wininst.py | 32 - Lib/packaging/tests/test_command_build.py | 56 - .../tests/test_command_build_clib.py | 141 -- Lib/packaging/tests/test_command_build_ext.py | 394 ----- Lib/packaging/tests/test_command_build_py.py | 146 -- .../tests/test_command_build_scripts.py | 109 -- Lib/packaging/tests/test_command_check.py | 161 -- Lib/packaging/tests/test_command_clean.py | 46 - Lib/packaging/tests/test_command_cmd.py | 102 -- Lib/packaging/tests/test_command_config.py | 76 - .../tests/test_command_install_data.py | 148 -- .../tests/test_command_install_dist.py | 241 --- .../tests/test_command_install_distinfo.py | 252 --- .../tests/test_command_install_headers.py | 38 - .../tests/test_command_install_lib.py | 110 -- .../tests/test_command_install_scripts.py | 75 - Lib/packaging/tests/test_command_register.py | 260 --- Lib/packaging/tests/test_command_sdist.py | 394 ----- Lib/packaging/tests/test_command_test.py | 224 --- Lib/packaging/tests/test_command_upload.py | 159 -- .../tests/test_command_upload_docs.py | 186 --- Lib/packaging/tests/test_compiler.py | 66 - Lib/packaging/tests/test_config.py | 519 ------ Lib/packaging/tests/test_create.py | 233 --- Lib/packaging/tests/test_cygwinccompiler.py | 88 - Lib/packaging/tests/test_database.py | 686 -------- Lib/packaging/tests/test_depgraph.py | 310 ---- Lib/packaging/tests/test_dist.py | 264 --- Lib/packaging/tests/test_extension.py | 15 - Lib/packaging/tests/test_install.py | 391 ----- Lib/packaging/tests/test_manifest.py | 331 ---- Lib/packaging/tests/test_markers.py | 75 - Lib/packaging/tests/test_metadata.py | 454 ----- Lib/packaging/tests/test_mixin2to3.py | 87 - Lib/packaging/tests/test_msvc9compiler.py | 140 -- Lib/packaging/tests/test_pypi_dist.py | 287 ---- Lib/packaging/tests/test_pypi_server.py | 88 - Lib/packaging/tests/test_pypi_simple.py | 353 ---- Lib/packaging/tests/test_pypi_xmlrpc.py | 101 -- Lib/packaging/tests/test_run.py | 92 - Lib/packaging/tests/test_support.py | 78 - Lib/packaging/tests/test_uninstall.py | 124 -- Lib/packaging/tests/test_unixccompiler.py | 132 -- Lib/packaging/tests/test_util.py | 1013 ----------- Lib/packaging/tests/test_version.py | 271 --- Lib/packaging/util.py | 1480 ----------------- Lib/packaging/version.py | 451 ----- Lib/sysconfig.cfg | 3 +- Lib/sysconfig.py | 2 +- Lib/test/regrtest.py | 41 - Lib/test/test_packaging.py | 5 - Lib/test/test_venv.py | 4 - Lib/venv/scripts/nt/pysetup3.py | 11 - Lib/venv/scripts/posix/pysetup3 | 11 - Makefile.pre.in | 54 - Misc/NEWS | 2 + Tools/scripts/pysetup3 | 4 - setup.py | 3 +- 246 files changed, 35 insertions(+), 38866 deletions(-) delete mode 100644 Doc/install/index.rst delete mode 100644 Doc/install/install.rst delete mode 100644 Doc/install/pysetup-config.rst delete mode 100644 Doc/install/pysetup-servers.rst delete mode 100644 Doc/install/pysetup.rst delete mode 100644 Doc/library/packaging-misc.rst delete mode 100644 Doc/library/packaging.command.rst delete mode 100644 Doc/library/packaging.compiler.rst delete mode 100644 Doc/library/packaging.database.rst delete mode 100644 Doc/library/packaging.depgraph.rst delete mode 100644 Doc/library/packaging.dist.rst delete mode 100644 Doc/library/packaging.fancy_getopt.rst delete mode 100644 Doc/library/packaging.install.rst delete mode 100644 Doc/library/packaging.metadata.rst delete mode 100644 Doc/library/packaging.pypi.dist.rst delete mode 100644 Doc/library/packaging.pypi.rst delete mode 100644 Doc/library/packaging.pypi.simple.rst delete mode 100644 Doc/library/packaging.pypi.xmlrpc.rst delete mode 100644 Doc/library/packaging.rst delete mode 100644 Doc/library/packaging.tests.pypi_server.rst delete mode 100644 Doc/library/packaging.util.rst delete mode 100644 Doc/library/packaging.version.rst delete mode 100644 Doc/packaging/builtdist.rst delete mode 100644 Doc/packaging/commandhooks.rst delete mode 100644 Doc/packaging/commandref.rst delete mode 100644 Doc/packaging/configfile.rst delete mode 100644 Doc/packaging/examples.rst delete mode 100644 Doc/packaging/extending.rst delete mode 100644 Doc/packaging/index.rst delete mode 100644 Doc/packaging/introduction.rst delete mode 100644 Doc/packaging/packageindex.rst delete mode 100644 Doc/packaging/setupcfg.rst delete mode 100644 Doc/packaging/setupscript.rst delete mode 100644 Doc/packaging/sourcedist.rst delete mode 100644 Doc/packaging/tutorial.rst delete mode 100644 Doc/packaging/uploading.rst delete mode 100644 Lib/packaging/__init__.py delete mode 100644 Lib/packaging/_trove.py delete mode 100644 Lib/packaging/command/__init__.py delete mode 100644 Lib/packaging/command/bdist.py delete mode 100644 Lib/packaging/command/bdist_dumb.py delete mode 100644 Lib/packaging/command/bdist_msi.py delete mode 100644 Lib/packaging/command/bdist_wininst.py delete mode 100644 Lib/packaging/command/build.py delete mode 100644 Lib/packaging/command/build_clib.py delete mode 100644 Lib/packaging/command/build_ext.py delete mode 100644 Lib/packaging/command/build_py.py delete mode 100644 Lib/packaging/command/build_scripts.py delete mode 100644 Lib/packaging/command/check.py delete mode 100644 Lib/packaging/command/clean.py delete mode 100644 Lib/packaging/command/cmd.py delete mode 100644 Lib/packaging/command/command_template delete mode 100644 Lib/packaging/command/config.py delete mode 100644 Lib/packaging/command/install_data.py delete mode 100644 Lib/packaging/command/install_dist.py delete mode 100644 Lib/packaging/command/install_distinfo.py delete mode 100644 Lib/packaging/command/install_headers.py delete mode 100644 Lib/packaging/command/install_lib.py delete mode 100644 Lib/packaging/command/install_scripts.py delete mode 100644 Lib/packaging/command/register.py delete mode 100644 Lib/packaging/command/sdist.py delete mode 100644 Lib/packaging/command/test.py delete mode 100644 Lib/packaging/command/upload.py delete mode 100644 Lib/packaging/command/upload_docs.py delete mode 100644 Lib/packaging/command/wininst-10.0-amd64.exe delete mode 100644 Lib/packaging/command/wininst-10.0.exe delete mode 100644 Lib/packaging/command/wininst-6.0.exe delete mode 100644 Lib/packaging/command/wininst-7.1.exe delete mode 100644 Lib/packaging/command/wininst-8.0.exe delete mode 100644 Lib/packaging/command/wininst-9.0-amd64.exe delete mode 100644 Lib/packaging/command/wininst-9.0.exe delete mode 100644 Lib/packaging/compat.py delete mode 100644 Lib/packaging/compiler/__init__.py delete mode 100644 Lib/packaging/compiler/bcppcompiler.py delete mode 100644 Lib/packaging/compiler/ccompiler.py delete mode 100644 Lib/packaging/compiler/cygwinccompiler.py delete mode 100644 Lib/packaging/compiler/extension.py delete mode 100644 Lib/packaging/compiler/msvc9compiler.py delete mode 100644 Lib/packaging/compiler/msvccompiler.py delete mode 100644 Lib/packaging/compiler/unixccompiler.py delete mode 100644 Lib/packaging/config.py delete mode 100644 Lib/packaging/create.py delete mode 100644 Lib/packaging/database.py delete mode 100644 Lib/packaging/depgraph.py delete mode 100644 Lib/packaging/dist.py delete mode 100644 Lib/packaging/errors.py delete mode 100644 Lib/packaging/fancy_getopt.py delete mode 100644 Lib/packaging/install.py delete mode 100644 Lib/packaging/manifest.py delete mode 100644 Lib/packaging/markers.py delete mode 100644 Lib/packaging/metadata.py delete mode 100644 Lib/packaging/pypi/__init__.py delete mode 100644 Lib/packaging/pypi/base.py delete mode 100644 Lib/packaging/pypi/dist.py delete mode 100644 Lib/packaging/pypi/errors.py delete mode 100644 Lib/packaging/pypi/mirrors.py delete mode 100644 Lib/packaging/pypi/simple.py delete mode 100644 Lib/packaging/pypi/wrapper.py delete mode 100644 Lib/packaging/pypi/xmlrpc.py delete mode 100644 Lib/packaging/run.py delete mode 100644 Lib/packaging/tests/LONG_DESC.txt delete mode 100644 Lib/packaging/tests/PKG-INFO delete mode 100644 Lib/packaging/tests/SETUPTOOLS-PKG-INFO delete mode 100644 Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 delete mode 100644 Lib/packaging/tests/__init__.py delete mode 100644 Lib/packaging/tests/__main__.py delete mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/INSTALLER delete mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA delete mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RECORD delete mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/REQUESTED delete mode 100644 Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES delete mode 100644 Lib/packaging/tests/fake_dists/babar.cfg delete mode 100644 Lib/packaging/tests/fake_dists/babar.png delete mode 100644 Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt delete mode 100644 Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt delete mode 100644 Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py delete mode 100644 Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py delete mode 100644 Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/RECORD delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py delete mode 100644 Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py delete mode 100644 Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info delete mode 100644 Lib/packaging/tests/fake_dists/strawberry-0.6.egg delete mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER delete mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA delete mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD delete mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED delete mode 100644 Lib/packaging/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py delete mode 100644 Lib/packaging/tests/fake_dists/truffles-5.0.egg-info delete mode 100644 Lib/packaging/tests/fixer/__init__.py delete mode 100644 Lib/packaging/tests/fixer/fix_echo.py delete mode 100644 Lib/packaging/tests/fixer/fix_echo2.py delete mode 100644 Lib/packaging/tests/pypi_server.py delete mode 100644 Lib/packaging/tests/pypi_test_server.py delete mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz delete mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/badmd5-0.1.tar.gz delete mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html delete mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html delete mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html delete mode 100644 Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/project_list/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/test_found_links/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html delete mode 100644 Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_externals/external/external.html delete mode 100644 Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_externals/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html delete mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html delete mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html delete mode 100644 Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html delete mode 100644 Lib/packaging/tests/support.py delete mode 100644 Lib/packaging/tests/test_ccompiler.py delete mode 100644 Lib/packaging/tests/test_command_bdist.py delete mode 100644 Lib/packaging/tests/test_command_bdist_dumb.py delete mode 100644 Lib/packaging/tests/test_command_bdist_msi.py delete mode 100644 Lib/packaging/tests/test_command_bdist_wininst.py delete mode 100644 Lib/packaging/tests/test_command_build.py delete mode 100644 Lib/packaging/tests/test_command_build_clib.py delete mode 100644 Lib/packaging/tests/test_command_build_ext.py delete mode 100644 Lib/packaging/tests/test_command_build_py.py delete mode 100644 Lib/packaging/tests/test_command_build_scripts.py delete mode 100644 Lib/packaging/tests/test_command_check.py delete mode 100644 Lib/packaging/tests/test_command_clean.py delete mode 100644 Lib/packaging/tests/test_command_cmd.py delete mode 100644 Lib/packaging/tests/test_command_config.py delete mode 100644 Lib/packaging/tests/test_command_install_data.py delete mode 100644 Lib/packaging/tests/test_command_install_dist.py delete mode 100644 Lib/packaging/tests/test_command_install_distinfo.py delete mode 100644 Lib/packaging/tests/test_command_install_headers.py delete mode 100644 Lib/packaging/tests/test_command_install_lib.py delete mode 100644 Lib/packaging/tests/test_command_install_scripts.py delete mode 100644 Lib/packaging/tests/test_command_register.py delete mode 100644 Lib/packaging/tests/test_command_sdist.py delete mode 100644 Lib/packaging/tests/test_command_test.py delete mode 100644 Lib/packaging/tests/test_command_upload.py delete mode 100644 Lib/packaging/tests/test_command_upload_docs.py delete mode 100644 Lib/packaging/tests/test_compiler.py delete mode 100644 Lib/packaging/tests/test_config.py delete mode 100644 Lib/packaging/tests/test_create.py delete mode 100644 Lib/packaging/tests/test_cygwinccompiler.py delete mode 100644 Lib/packaging/tests/test_database.py delete mode 100644 Lib/packaging/tests/test_depgraph.py delete mode 100644 Lib/packaging/tests/test_dist.py delete mode 100644 Lib/packaging/tests/test_extension.py delete mode 100644 Lib/packaging/tests/test_install.py delete mode 100644 Lib/packaging/tests/test_manifest.py delete mode 100644 Lib/packaging/tests/test_markers.py delete mode 100644 Lib/packaging/tests/test_metadata.py delete mode 100644 Lib/packaging/tests/test_mixin2to3.py delete mode 100644 Lib/packaging/tests/test_msvc9compiler.py delete mode 100644 Lib/packaging/tests/test_pypi_dist.py delete mode 100644 Lib/packaging/tests/test_pypi_server.py delete mode 100644 Lib/packaging/tests/test_pypi_simple.py delete mode 100644 Lib/packaging/tests/test_pypi_xmlrpc.py delete mode 100644 Lib/packaging/tests/test_run.py delete mode 100644 Lib/packaging/tests/test_support.py delete mode 100644 Lib/packaging/tests/test_uninstall.py delete mode 100644 Lib/packaging/tests/test_unixccompiler.py delete mode 100644 Lib/packaging/tests/test_util.py delete mode 100644 Lib/packaging/tests/test_version.py delete mode 100644 Lib/packaging/util.py delete mode 100644 Lib/packaging/version.py delete mode 100644 Lib/test/test_packaging.py delete mode 100644 Lib/venv/scripts/nt/pysetup3.py delete mode 100644 Lib/venv/scripts/posix/pysetup3 delete mode 100755 Tools/scripts/pysetup3 diff --git a/Doc/contents.rst b/Doc/contents.rst index cc5c8e379496..c0c6af34d9d1 100644 --- a/Doc/contents.rst +++ b/Doc/contents.rst @@ -11,7 +11,7 @@ library/index.rst extending/index.rst c-api/index.rst - packaging/index.rst + distutils/index.rst install/index.rst howto/index.rst faq/index.rst diff --git a/Doc/distutils/index.rst b/Doc/distutils/index.rst index c8dd9f46a8ce..ace8280894ed 100644 --- a/Doc/distutils/index.rst +++ b/Doc/distutils/index.rst @@ -14,12 +14,9 @@ the module developer's point of view, describing how to use the Distutils to make Python modules and extensions easily available to a wider audience with very little overhead for build/release/install mechanics. -.. deprecated:: 3.3 - :mod:`packaging` replaces Distutils. See :ref:`packaging-index` and - :ref:`packaging-install-index`. - .. toctree:: :maxdepth: 2 + :numbered: introduction.rst setupscript.rst @@ -32,10 +29,3 @@ very little overhead for build/release/install mechanics. extending.rst commandref.rst apiref.rst - -Another document describes how to install modules and extensions packaged -following the above guidelines: - -.. toctree:: - - install.rst diff --git a/Doc/install/index.rst b/Doc/install/index.rst deleted file mode 100644 index bb2e9c58f179..000000000000 --- a/Doc/install/index.rst +++ /dev/null @@ -1,56 +0,0 @@ -.. _packaging-install-index: - -****************************** - Installing Python Projects -****************************** - -:Author: The Fellowship of the Packaging -:Release: |version| -:Date: |today| - -.. TODO: Fill in XXX comments - -.. The audience for this document includes people who don't know anything - about Python and aren't about to learn the language just in order to - install and maintain it for their users, i.e. system administrators. - Thus, I have to be sure to explain the basics at some point: - sys.path and PYTHONPATH at least. Should probably give pointers to - other docs on "import site", PYTHONSTARTUP, PYTHONHOME, etc. - - Finally, it might be useful to include all the material from my "Care - and Feeding of a Python Installation" talk in here somewhere. Yow! - -.. topic:: Abstract - - This document describes Packaging from the end-user's point of view: it - explains how to extend the functionality of a standard Python installation by - building and installing third-party Python modules and applications. - - -This guide is split into a simple overview followed by a longer presentation of -the :program:`pysetup` script, the Python package management tool used to -build, distribute, search for, install, remove and list Python distributions. - -.. TODO integrate install and pysetup instead of duplicating - -.. toctree:: - :maxdepth: 2 - :numbered: - - install - pysetup - pysetup-config - pysetup-servers - - -.. seealso:: - - :ref:`packaging-index` - The manual for developers of Python projects who want to package and - distribute them. This describes how to use :mod:`packaging` to make - projects easily found and added to an existing Python installation. - - :mod:`packaging` - A library reference for developers of packaging tools wanting to use - standalone building blocks like :mod:`~packaging.version` or - :mod:`~packaging.metadata`, or extend Packaging itself. diff --git a/Doc/install/install.rst b/Doc/install/install.rst deleted file mode 100644 index b3e655b0698d..000000000000 --- a/Doc/install/install.rst +++ /dev/null @@ -1,1119 +0,0 @@ -.. highlightlang:: none - -==================================== -Installing Python projects: overview -==================================== - -.. _packaging-install-intro: - -Introduction -============ - -Although Python's extensive standard library covers many programming needs, -there often comes a time when you need to add new functionality to your Python -installation in the form of third-party modules. This might be necessary to -support your own programming, or to support an application that you want to use -and that happens to be written in Python. - -In the past, there was little support for adding third-party modules to an -existing Python installation. With the introduction of the Python Distribution -Utilities (Distutils for short) in Python 2.0, this changed. However, not all -problems were solved; end-users had to rely on ``easy_install`` or -``pip`` to download third-party modules from PyPI, uninstall distributions or do -other maintenance operations. Packaging is a more complete replacement for -Distutils, in the standard library, with a backport named Distutils2 available -for older Python versions. - -This document is aimed primarily at people who need to install third-party -Python modules: end-users and system administrators who just need to get some -Python application running, and existing Python programmers who want to add -new goodies to their toolbox. You don't need to know Python to read this -document; there will be some brief forays into using Python's interactive mode -to explore your installation, but that's it. If you're looking for information -on how to distribute your own Python modules so that others may use them, see -the :ref:`packaging-index` manual. - - -.. _packaging-trivial-install: - -Best case: trivial installation -------------------------------- - -In the best case, someone will have prepared a special version of the module -distribution you want to install that is targeted specifically at your platform -and can be installed just like any other software on your platform. For example, -the module's developer might make an executable installer available for Windows -users, an RPM package for users of RPM-based Linux systems (Red Hat, SuSE, -Mandrake, and many others), a Debian package for users of Debian and derivative -systems, and so forth. - -In that case, you would use the standard system tools to download and install -the specific installer for your platform and its dependencies. - -Of course, things will not always be that easy. You might be interested in a -module whose distribution doesn't have an easy-to-use installer for your -platform. In that case, you'll have to start with the source distribution -released by the module's author/maintainer. Installing from a source -distribution is not too hard, as long as the modules are packaged in the -standard way. The bulk of this document addresses the building and installing -of modules from standard source distributions. - - -.. _packaging-distutils: - -The Python standard: Distutils ------------------------------- - -If you download a source distribution of a module, it will be obvious whether -it was packaged and distributed using Distutils. First, the distribution's name -and version number will be featured prominently in the name of the downloaded -archive, e.g. :file:`foo-1.0.tar.gz` or :file:`widget-0.9.7.zip`. Next, the -archive will unpack into a similarly-named directory: :file:`foo-1.0` or -:file:`widget-0.9.7`. Additionally, the distribution may contain a -:file:`setup.cfg` file and a file named :file:`README.txt` ---or possibly just -:file:`README`--- explaining that building and installing the module -distribution is a simple matter of issuing the following command at your shell's -prompt:: - - python setup.py install - -Third-party projects have extended Distutils to work around its limitations or -add functionality. After some years of near-inactivity in Distutils, a new -maintainer has started to standardize good ideas in PEPs and implement them in a -new, improved version of Distutils, called Distutils2 or Packaging. - - -.. _packaging-new-standard: - -The new standard: Packaging ---------------------------- - -The rules described in the first paragraph above apply to Packaging-based -projects too: a source distribution will have a name like -:file:`widget-0.9.7.zip`. One of the main differences with Distutils is that -distributions no longer have a :file:`setup.py` script; it used to cause a -number of issues. Now there is a unique script installed with Python itself:: - - pysetup install widget-0.9.7.zip - -Running this command is enough to build and install projects (Python modules or -packages, scripts or whole applications), without even having to unpack the -archive. It is also compatible with Distutils-based distributions. - -Unless you have to perform non-standard installations or customize the build -process, you can stop reading this manual ---the above command is everything you -need to get out of it. - -With :program:`pysetup`, you won't even have to manually download a distribution -before installing it; see :ref:`packaging-pysetup`. - - -.. _packaging-standard-install: - -Standard build and install -========================== - -As described in section :ref:`packaging-new-standard`, building and installing -a module distribution using Packaging usually comes down to one simple -command:: - - pysetup run install_dist - -This is a command that should be run in a terminal. On Windows, it is called a -command prompt and found in :menuselection:`Start --> Accessories`; Powershell -is a popular alternative. - - -.. _packaging-platform-variations: - -Platform variations -------------------- - -The setup command is meant to be run from the root directory of the source -distribution, i.e. the top-level subdirectory that the module source -distribution unpacks into. For example, if you've just downloaded a module -source distribution :file:`foo-1.0.tar.gz` onto a Unix system, the normal -steps to follow are these:: - - gunzip -c foo-1.0.tar.gz | tar xf - # unpacks into directory foo-1.0 - cd foo-1.0 - pysetup run install_dist - -On Windows, you'd probably download :file:`foo-1.0.zip`. If you downloaded the -archive file to :file:`C:\\Temp`, then it would unpack into -:file:`C:\\Temp\\foo-1.0`. To actually unpack the archive, you can use either -an archive manipulator with a graphical user interface (such as WinZip or 7-Zip) -or a command-line tool (such as :program:`unzip`, :program:`pkunzip` or, again, -:program:`7z`). Then, open a command prompt window and run:: - - cd c:\Temp\foo-1.0 - pysetup run install_dist - - -.. _packaging-splitting-up: - -Splitting the job up --------------------- - -Running ``pysetup run install_dist`` builds and installs all modules in one go. If you -prefer to work incrementally ---especially useful if you want to customize the -build process, or if things are going wrong--- you can use the setup script to -do one thing at a time. This is a valuable tool when different users will perform -separately the build and install steps. For example, you might want to build a -module distribution and hand it off to a system administrator for installation -(or do it yourself, but with super-user or admin privileges). - -For example, to build everything in one step and then install everything -in a second step, you aptly invoke two distinct Packaging commands:: - - pysetup run build - pysetup run install_dist - -If you do this, you will notice that invoking the :command:`install_dist` command -first runs the :command:`build` command, which ---in this case--- quickly -notices it can spare itself the work, since everything in the :file:`build` -directory is up-to-date. - -You may often ignore this ability to divide the process in steps if all you do -is installing modules downloaded from the Internet, but it's very handy for -more advanced tasks. If you find yourself in the need for distributing your own -Python modules and extensions, though, you'll most likely run many individual -Packaging commands. - - -.. _packaging-how-build-works: - -How building works ------------------- - -As implied above, the :command:`build` command is responsible for collecting -and placing the files to be installed into a *build directory*. By default, -this is :file:`build`, under the distribution root. If you're excessively -concerned with speed, or want to keep the source tree pristine, you can specify -a different build directory with the :option:`--build-base` option. For example:: - - pysetup run build --build-base /tmp/pybuild/foo-1.0 - -(Or you could do this permanently with a directive in your system or personal -Packaging configuration file; see section :ref:`packaging-config-files`.) -In the usual case, however, all this is unnecessary. - -The build tree's default layout looks like so:: - - --- build/ --- lib/ - or - --- build/ --- lib./ - temp./ - -where ```` expands to a brief description of the current OS/hardware -platform and Python version. The first form, with just a :file:`lib` directory, -is used for pure module distributions (module distributions that -include only pure Python modules). If a module distribution contains any -extensions (modules written in C/C++), then the second form, with two ```` -directories, is used. In that case, the :file:`temp.{plat}` directory holds -temporary files generated during the compile/link process which are not intended -to be installed. In either case, the :file:`lib` (or :file:`lib.{plat}`) directory -contains all Python modules (pure Python and extensions) to be installed. - -In the future, more directories will be added to handle Python scripts, -documentation, binary executables, and whatever else is required to install -Python modules and applications. - - -.. _packaging-how-install-works: - -How installation works ----------------------- - -After the :command:`build` command is run (whether explicitly or by the -:command:`install_dist` command on your behalf), the work of the :command:`install_dist` -command is relatively simple: all it has to do is copy the contents of -:file:`build/lib` (or :file:`build/lib.{plat}`) to the installation directory -of your choice. - -If you don't choose an installation directory ---i.e., if you just run -``pysetup run install_dist``\ --- then the :command:`install_dist` command -installs to the standard location for third-party Python modules. This location -varies by platform and depending on how you built/installed Python itself. On -Unix (and Mac OS X, which is also Unix-based), it also depends on whether the -module distribution being installed is pure Python or contains extensions -("non-pure"): - -+-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ -| Platform | Standard installation location | Default value | Notes | -+=================+=====================================================+==================================================+=======+ -| Unix (pure) | :file:`{prefix}/lib/python{X.Y}/site-packages` | :file:`/usr/local/lib/python{X.Y}/site-packages` | \(1) | -+-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ -| Unix (non-pure) | :file:`{exec-prefix}/lib/python{X.Y}/site-packages` | :file:`/usr/local/lib/python{X.Y}/site-packages` | \(1) | -+-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ -| Windows | :file:`{prefix}\\Lib\\site-packages` | :file:`C:\\Python{XY}\\Lib\\site-packages` | \(2) | -+-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ - -Notes: - -(1) - Most Linux distributions include Python as a standard part of the system, so - :file:`{prefix}` and :file:`{exec-prefix}` are usually both :file:`/usr` on - Linux. If you build Python yourself on Linux (or any Unix-like system), the - default :file:`{prefix}` and :file:`{exec-prefix}` are :file:`/usr/local`. - -(2) - The default installation directory on Windows was :file:`C:\\Program - Files\\Python` under Python 1.6a1, 1.5.2, and earlier. - -:file:`{prefix}` and :file:`{exec-prefix}` stand for the directories that Python -is installed to, and where it finds its libraries at run-time. They are always -the same under Windows, and very often the same under Unix and Mac OS X. You -can find out what your Python installation uses for :file:`{prefix}` and -:file:`{exec-prefix}` by running Python in interactive mode and typing a few -simple commands. - -.. TODO link to Doc/using instead of duplicating - -To start the interactive Python interpreter, you need to follow a slightly -different recipe for each platform. Under Unix, just type :command:`python` at -the shell prompt. Under Windows (assuming the Python executable is on your -:envvar:`PATH`, which is the usual case), you can choose :menuselection:`Start --> Run`, -type ``python`` and press ``enter``. Alternatively, you can simply execute -:command:`python` at a command prompt (:menuselection:`Start --> Accessories`) -or in Powershell. - -Once the interpreter is started, you type Python code at the prompt. For -example, on my Linux system, I type the three Python statements shown below, -and get the output as shown, to find out my :file:`{prefix}` and :file:`{exec-prefix}`:: - - Python 3.3 (r32:88445, Apr 2 2011, 10:43:54) - Type "help", "copyright", "credits" or "license" for more information. - >>> import sys - >>> sys.prefix - '/usr' - >>> sys.exec_prefix - '/usr' - -A few other placeholders are used in this document: :file:`{X.Y}` stands for the -version of Python, for example ``3.2``; :file:`{abiflags}` will be replaced by -the value of :data:`sys.abiflags` or the empty string for platforms which don't -define ABI flags; :file:`{distname}` will be replaced by the name of the module -distribution being installed. Dots and capitalization are important in the -paths; for example, a value that uses ``python3.2`` on UNIX will typically use -``Python32`` on Windows. - -If you don't want to install modules to the standard location, or if you don't -have permission to write there, then you need to read about alternate -installations in section :ref:`packaging-alt-install`. If you want to customize your -installation directories more heavily, see section :ref:`packaging-custom-install`. - - -.. _packaging-alt-install: - -Alternate installation -====================== - -Often, it is necessary or desirable to install modules to a location other than -the standard location for third-party Python modules. For example, on a Unix -system you might not have permission to write to the standard third-party module -directory. Or you might wish to try out a module before making it a standard -part of your local Python installation. This is especially true when upgrading -a distribution already present: you want to make sure your existing base of -scripts still works with the new version before actually upgrading. - -The Packaging :command:`install_dist` command is designed to make installing module -distributions to an alternate location simple and painless. The basic idea is -that you supply a base directory for the installation, and the -:command:`install_dist` command picks a set of directories (called an *installation -scheme*) under this base directory in which to install files. The details -differ across platforms, so read whichever of the following sections applies to -you. - -Note that the various alternate installation schemes are mutually exclusive: you -can pass ``--user``, or ``--home``, or ``--prefix`` and ``--exec-prefix``, or -``--install-base`` and ``--install-platbase``, but you can't mix from these -groups. - - -.. _packaging-alt-install-user: - -Alternate installation: the user scheme ---------------------------------------- - -This scheme is designed to be the most convenient solution for users that don't -have write permission to the global site-packages directory or don't want to -install into it. It is enabled with a simple option:: - - pysetup run install_dist --user - -Files will be installed into subdirectories of :data:`site.USER_BASE` (written -as :file:`{userbase}` hereafter). This scheme installs pure Python modules and -extension modules in the same location (also known as :data:`site.USER_SITE`). -Here are the values for UNIX, including non-framework builds on Mac OS X: - -=============== =========================================================== -Type of file Installation directory -=============== =========================================================== -modules :file:`{userbase}/lib/python{X.Y}/site-packages` -scripts :file:`{userbase}/bin` -data :file:`{userbase}` -C headers :file:`{userbase}/include/python{X.Y}` -=============== =========================================================== - -Framework builds on Mac OS X use these paths: - -=============== =========================================================== -Type of file Installation directory -=============== =========================================================== -modules :file:`{userbase}/lib/python/site-packages` -scripts :file:`{userbase}/bin` -data :file:`{userbase}` -C headers :file:`{userbase}/include/python` -=============== =========================================================== - -And here are the values used on Windows: - -=============== =========================================================== -Type of file Installation directory -=============== =========================================================== -modules :file:`{userbase}\\Python{XY}\\site-packages` -scripts :file:`{userbase}\\Scripts` -data :file:`{userbase}` -C headers :file:`{userbase}\\Python{XY}\\Include` -=============== =========================================================== - -The advantage of using this scheme compared to the other ones described below is -that the user site-packages directory is under normal conditions always included -in :data:`sys.path` (see :mod:`site` for more information), which means that -there is no additional step to perform after running ``pysetup`` to finalize the -installation. - -The :command:`build_ext` command also has a ``--user`` option to add -:file:`{userbase}/include` to the compiler search path for header files and -:file:`{userbase}/lib` to the compiler search path for libraries as well as to -the runtime search path for shared C libraries (rpath). - - -.. _packaging-alt-install-home: - -Alternate installation: the home scheme ---------------------------------------- - -The idea behind the "home scheme" is that you build and maintain a personal -stash of Python modules. This scheme's name is derived from the concept of a -"home" directory on Unix, since it's not unusual for a Unix user to make their -home directory have a layout similar to :file:`/usr/` or :file:`/usr/local/`. -In spite of its name's origin, this scheme can be used by anyone, regardless -of the operating system. - -Installing a new module distribution in this way is as simple as :: - - pysetup run install_dist --home - -where you can supply any directory you like for the :option:`--home` option. On -Unix, lazy typists can just type a tilde (``~``); the :command:`install_dist` command -will expand this to your home directory:: - - pysetup run install_dist --home ~ - -To make Python find the distributions installed with this scheme, you may have -to :ref:`modify Python's search path ` or edit -:mod:`sitecustomize` (see :mod:`site`) to call :func:`site.addsitedir` or edit -:data:`sys.path`. - -The :option:`--home` option defines the base directory for the installation. -Under it, files are installed to the following directories: - -=============== =========================================================== -Type of file Installation directory -=============== =========================================================== -modules :file:`{home}/lib/python` -scripts :file:`{home}/bin` -data :file:`{home}` -C headers :file:`{home}/include/python` -=============== =========================================================== - -(Mentally replace slashes with backslashes if you're on Windows.) - - -.. _packaging-alt-install-prefix-unix: - -Alternate installation: Unix (the prefix scheme) ------------------------------------------------- - -The "prefix scheme" is useful when you wish to use one Python installation to -run the build command, but install modules into the third-party module directory -of a different Python installation (or something that looks like a different -Python installation). If this sounds a trifle unusual, it is ---that's why the -user and home schemes come before. However, there are at least two known cases -where the prefix scheme will be useful. - -First, consider that many Linux distributions put Python in :file:`/usr`, rather -than the more traditional :file:`/usr/local`. This is entirely appropriate, -since in those cases Python is part of "the system" rather than a local add-on. -However, if you are installing Python modules from source, you probably want -them to go in :file:`/usr/local/lib/python2.{X}` rather than -:file:`/usr/lib/python2.{X}`. This can be done with :: - - pysetup run install_dist --prefix /usr/local - -Another possibility is a network filesystem where the name used to write to a -remote directory is different from the name used to read it: for example, the -Python interpreter accessed as :file:`/usr/local/bin/python` might search for -modules in :file:`/usr/local/lib/python2.{X}`, but those modules would have to -be installed to, say, :file:`/mnt/{@server}/export/lib/python2.{X}`. This could -be done with :: - - pysetup run install_dist --prefix=/mnt/@server/export - -In either case, the :option:`--prefix` option defines the installation base, and -the :option:`--exec-prefix` option defines the platform-specific installation -base, which is used for platform-specific files. (Currently, this just means -non-pure module distributions, but could be expanded to C libraries, binary -executables, etc.) If :option:`--exec-prefix` is not supplied, it defaults to -:option:`--prefix`. Files are installed as follows: - -================= ========================================================== -Type of file Installation directory -================= ========================================================== -Python modules :file:`{prefix}/lib/python{X.Y}/site-packages` -extension modules :file:`{exec-prefix}/lib/python{X.Y}/site-packages` -scripts :file:`{prefix}/bin` -data :file:`{prefix}` -C headers :file:`{prefix}/include/python{X.Y}{abiflags}` -================= ========================================================== - -.. XXX misses an entry for platinclude - -There is no requirement that :option:`--prefix` or :option:`--exec-prefix` -actually point to an alternate Python installation; if the directories listed -above do not already exist, they are created at installation time. - -Incidentally, the real reason the prefix scheme is important is simply that a -standard Unix installation uses the prefix scheme, but with :option:`--prefix` -and :option:`--exec-prefix` supplied by Python itself as ``sys.prefix`` and -``sys.exec_prefix``. Thus, you might think you'll never use the prefix scheme, -but every time you run ``pysetup run install_dist`` without any other -options, you're using it. - -Note that installing extensions to an alternate Python installation doesn't have -anything to do with how those extensions are built: in particular, extensions -will be compiled using the Python header files (:file:`Python.h` and friends) -installed with the Python interpreter used to run the build command. It is -therefore your responsibility to ensure compatibility between the interpreter -intended to run extensions installed in this way and the interpreter used to -build these same extensions. To avoid problems, it is best to make sure that -the two interpreters are the same version of Python (possibly different builds, -or possibly copies of the same build). (Of course, if your :option:`--prefix` -and :option:`--exec-prefix` don't even point to an alternate Python installation, -this is immaterial.) - - -.. _packaging-alt-install-prefix-windows: - -Alternate installation: Windows (the prefix scheme) ---------------------------------------------------- - -Windows has a different and vaguer notion of home directories than Unix, and -since its standard Python installation is simpler, the :option:`--prefix` option -has traditionally been used to install additional packages to arbitrary -locations. :: - - pysetup run install_dist --prefix "\Temp\Python" - -to install modules to the :file:`\\Temp\\Python` directory on the current drive. - -The installation base is defined by the :option:`--prefix` option; the -:option:`--exec-prefix` option is not supported under Windows, which means that -pure Python modules and extension modules are installed into the same location. -Files are installed as follows: - -=============== ========================================================== -Type of file Installation directory -=============== ========================================================== -modules :file:`{prefix}\\Lib\\site-packages` -scripts :file:`{prefix}\\Scripts` -data :file:`{prefix}` -C headers :file:`{prefix}\\Include` -=============== ========================================================== - - -.. _packaging-custom-install: - -Custom installation -=================== - -Sometimes, the alternate installation schemes described in section -:ref:`packaging-alt-install` just don't do what you want. You might want to tweak -just one or two directories while keeping everything under the same base -directory, or you might want to completely redefine the installation scheme. -In either case, you're creating a *custom installation scheme*. - -To create a custom installation scheme, you start with one of the alternate -schemes and override some of the installation directories used for the various -types of files, using these options: - -====================== ======================= -Type of file Override option -====================== ======================= -Python modules ``--install-purelib`` -extension modules ``--install-platlib`` -all modules ``--install-lib`` -scripts ``--install-scripts`` -data ``--install-data`` -C headers ``--install-headers`` -====================== ======================= - -These override options can be relative, absolute, -or explicitly defined in terms of one of the installation base directories. -(There are two installation base directories, and they are normally the same ----they only differ when you use the Unix "prefix scheme" and supply different -``--prefix`` and ``--exec-prefix`` options; using ``--install-lib`` will -override values computed or given for ``--install-purelib`` and -``--install-platlib``, and is recommended for schemes that don't make a -difference between Python and extension modules.) - -For example, say you're installing a module distribution to your home directory -under Unix, but you want scripts to go in :file:`~/scripts` rather than -:file:`~/bin`. As you might expect, you can override this directory with the -:option:`--install-scripts` option and, in this case, it makes most sense to supply -a relative path, which will be interpreted relative to the installation base -directory (in our example, your home directory):: - - pysetup run install_dist --home ~ --install-scripts scripts - -Another Unix example: suppose your Python installation was built and installed -with a prefix of :file:`/usr/local/python`. Thus, in a standard installation, -scripts will wind up in :file:`/usr/local/python/bin`. If you want them in -:file:`/usr/local/bin` instead, you would supply this absolute directory for -the :option:`--install-scripts` option:: - - pysetup run install_dist --install-scripts /usr/local/bin - -This command performs an installation using the "prefix scheme", where the -prefix is whatever your Python interpreter was installed with ---in this case, -:file:`/usr/local/python`. - -If you maintain Python on Windows, you might want third-party modules to live in -a subdirectory of :file:`{prefix}`, rather than right in :file:`{prefix}` -itself. This is almost as easy as customizing the script installation directory ----you just have to remember that there are two types of modules to worry about, -Python and extension modules, which can conveniently be both controlled by one -option:: - - pysetup run install_dist --install-lib Site - -.. XXX Nothing is installed right under prefix in windows, is it?? - -The specified installation directory is relative to :file:`{prefix}`. Of -course, you also have to ensure that this directory is in Python's module -search path, such as by putting a :file:`.pth` file in a site directory (see -:mod:`site`). See section :ref:`packaging-search-path` to find out how to modify -Python's search path. - -If you want to define an entire installation scheme, you just have to supply all -of the installation directory options. Using relative paths is recommended here. -For example, if you want to maintain all Python module-related files under -:file:`python` in your home directory, and you want a separate directory for -each platform that you use your home directory from, you might define the -following installation scheme:: - - pysetup run install_dist --home ~ \ - --install-purelib python/lib \ - --install-platlib python/'lib.$PLAT' \ - --install-scripts python/scripts \ - --install-data python/data - -or, equivalently, :: - - pysetup run install_dist --home ~/python \ - --install-purelib lib \ - --install-platlib 'lib.$PLAT' \ - --install-scripts scripts \ - --install-data data - -``$PLAT`` doesn't need to be defined as an environment variable ---it will also -be expanded by Packaging as it parses your command line options, just as it -does when parsing your configuration file(s). (More on that later.) - -Obviously, specifying the entire installation scheme every time you install a -new module distribution would be very tedious. To spare you all that work, you -can store it in a Packaging configuration file instead (see section -:ref:`packaging-config-files`), like so:: - - [install_dist] - install-base = $HOME - install-purelib = python/lib - install-platlib = python/lib.$PLAT - install-scripts = python/scripts - install-data = python/data - -or, equivalently, :: - - [install_dist] - install-base = $HOME/python - install-purelib = lib - install-platlib = lib.$PLAT - install-scripts = scripts - install-data = data - -Note that these two are *not* equivalent if you override their installation -base directory when running the setup script. For example, :: - - pysetup run install_dist --install-base /tmp - -would install pure modules to :file:`/tmp/python/lib` in the first case, and -to :file:`/tmp/lib` in the second case. (For the second case, you'd probably -want to supply an installation base of :file:`/tmp/python`.) - -You may have noticed the use of ``$HOME`` and ``$PLAT`` in the sample -configuration file. These are Packaging configuration variables, which -bear a strong resemblance to environment variables. In fact, you can use -environment variables in configuration files on platforms that have such a notion, but -Packaging additionally defines a few extra variables that may not be in your -environment, such as ``$PLAT``. Of course, on systems that don't have -environment variables, such as Mac OS 9, the configuration variables supplied by -the Packaging are the only ones you can use. See section :ref:`packaging-config-files` -for details. - -.. XXX which vars win out eventually in case of clash env or Packaging? - -.. XXX need some Windows examples---when would custom installation schemes be - needed on those platforms? - - -.. XXX Move this section to Doc/using - -.. _packaging-search-path: - -Modifying Python's search path ------------------------------- - -When the Python interpreter executes an :keyword:`import` statement, it searches -for both Python code and extension modules along a search path. A default value -for this path is configured into the Python binary when the interpreter is built. -You can obtain the search path by importing the :mod:`sys` module and printing -the value of ``sys.path``. :: - - $ python - Python 2.2 (#11, Oct 3 2002, 13:31:27) - [GCC 2.96 20000731 (Red Hat Linux 7.3 2.96-112)] on linux2 - Type "help", "copyright", "credits" or "license" for more information. - >>> import sys - >>> sys.path - ['', '/usr/local/lib/python2.3', '/usr/local/lib/python2.3/plat-linux2', - '/usr/local/lib/python2.3/lib-tk', '/usr/local/lib/python2.3/lib-dynload', - '/usr/local/lib/python2.3/site-packages'] - >>> - -The null string in ``sys.path`` represents the current working directory. - -The expected convention for locally installed packages is to put them in the -:file:`{...}/site-packages/` directory, but you may want to choose a different -location for some reason. For example, if your site kept by convention all web -server-related software under :file:`/www`. Add-on Python modules might then -belong in :file:`/www/python`, and in order to import them, this directory would -have to be added to ``sys.path``. There are several ways to solve this problem. - -The most convenient way is to add a path configuration file to a directory -that's already on Python's path, usually to the :file:`.../site-packages/` -directory. Path configuration files have an extension of :file:`.pth`, and each -line must contain a single path that will be appended to ``sys.path``. (Because -the new paths are appended to ``sys.path``, modules in the added directories -will not override standard modules. This means you can't use this mechanism for -installing fixed versions of standard modules.) - -Paths can be absolute or relative, in which case they're relative to the -directory containing the :file:`.pth` file. See the documentation of -the :mod:`site` module for more information. - -A slightly less convenient way is to edit the :file:`site.py` file in Python's -standard library, and modify ``sys.path``. :file:`site.py` is automatically -imported when the Python interpreter is executed, unless the :option:`-S` switch -is supplied to suppress this behaviour. So you could simply edit -:file:`site.py` and add two lines to it:: - - import sys - sys.path.append('/www/python/') - -However, if you reinstall the same major version of Python (perhaps when -upgrading from 3.3 to 3.3.1, for example) :file:`site.py` will be overwritten by -the stock version. You'd have to remember that it was modified and save a copy -before doing the installation. - -Alternatively, there are two environment variables that can modify ``sys.path``. -:envvar:`PYTHONHOME` sets an alternate value for the prefix of the Python -installation. For example, if :envvar:`PYTHONHOME` is set to ``/www/python``, -the search path will be set to ``['', '/www/python/lib/pythonX.Y/', -'/www/python/lib/pythonX.Y/plat-linux2', ...]``. - -The :envvar:`PYTHONPATH` variable can be set to a list of paths that will be -added to the beginning of ``sys.path``. For example, if :envvar:`PYTHONPATH` is -set to ``/www/python:/opt/py``, the search path will begin with -``['/www/python', '/opt/py']``. (Note that directories must exist in order to -be added to ``sys.path``; the :mod:`site` module removes non-existent paths.) - -Finally, ``sys.path`` is just a regular Python list, so any Python application -can modify it by adding or removing entries. - - -.. _packaging-config-files: - -Configuration files for Packaging -================================= - -As mentioned above, you can use configuration files to store personal or site -preferences for any option supported by any Packaging command. Depending on your -platform, you can use one of two or three possible configuration files. These -files will be read before parsing the command-line, so they take precedence over -default values. In turn, the command-line will override configuration files. -Lastly, if there are multiple configuration files, values from files read -earlier will be overridden by values from files read later. - -.. XXX "one of two or three possible..." seems wrong info. Below always 3 files - are indicated in the tables. - - -.. _packaging-config-filenames: - -Location and names of configuration files ------------------------------------------ - -The name and location of the configuration files vary slightly across -platforms. On Unix and Mac OS X, these are the three configuration files listed -in the order they are processed: - -+--------------+----------------------------------------------------------+-------+ -| Type of file | Location and filename | Notes | -+==============+==========================================================+=======+ -| system | :file:`{prefix}/lib/python{ver}/packaging/packaging.cfg` | \(1) | -+--------------+----------------------------------------------------------+-------+ -| personal | :file:`$HOME/.pydistutils.cfg` | \(2) | -+--------------+----------------------------------------------------------+-------+ -| local | :file:`setup.cfg` | \(3) | -+--------------+----------------------------------------------------------+-------+ - -Similarly, the configuration files on Windows ---also listed in the order they -are processed--- are these: - -+--------------+-------------------------------------------------+-------+ -| Type of file | Location and filename | Notes | -+==============+=================================================+=======+ -| system | :file:`{prefix}\\Lib\\packaging\\packaging.cfg` | \(4) | -+--------------+-------------------------------------------------+-------+ -| personal | :file:`%HOME%\\pydistutils.cfg` | \(5) | -+--------------+-------------------------------------------------+-------+ -| local | :file:`setup.cfg` | \(3) | -+--------------+-------------------------------------------------+-------+ - -On all platforms, the *personal* file can be temporarily disabled by -means of the `--no-user-cfg` option. - -Notes: - -(1) - Strictly speaking, the system-wide configuration file lives in the directory - where Packaging is installed. - -(2) - On Unix, if the :envvar:`HOME` environment variable is not defined, the - user's home directory will be determined with the :func:`getpwuid` function - from the standard :mod:`pwd` module. Packaging uses the - :func:`os.path.expanduser` function to do this. - -(3) - I.e., in the current directory (usually the location of the setup script). - -(4) - (See also note (1).) Python's default installation prefix is - :file:`C:\\Python`, so the system configuration file is normally - :file:`C:\\Python\\Lib\\packaging\\packaging.cfg`. - -(5) - On Windows, if the :envvar:`HOME` environment variable is not defined, - :envvar:`USERPROFILE` then :envvar:`HOMEDRIVE` and :envvar:`HOMEPATH` will - be tried. Packaging uses the :func:`os.path.expanduser` function to do this. - - -.. _packaging-config-syntax: - -Syntax of configuration files ------------------------------ - -All Packaging configuration files share the same syntax. Options defined in -them are grouped into sections, and each Packaging command gets its own section. -Additionally, there's a ``global`` section for options that affect every command. -Sections consist of one or more lines containing a single option specified as -``option = value``. - -.. XXX use dry-run in the next example or use a pysetup option as example - -For example, here's a complete configuration file that forces all commands to -run quietly by default:: - - [global] - verbose = 0 - -If this was the system configuration file, it would affect all processing -of any Python module distribution by any user on the current system. If it was -installed as your personal configuration file (on systems that support them), -it would affect only module distributions processed by you. Lastly, if it was -used as the :file:`setup.cfg` for a particular module distribution, it would -affect that distribution only. - -.. XXX "(on systems that support them)" seems wrong info - -If you wanted to, you could override the default "build base" directory and -make the :command:`build\*` commands always forcibly rebuild all files with -the following:: - - [build] - build-base = blib - force = 1 - -which corresponds to the command-line arguments:: - - pysetup run build --build-base blib --force - -except that including the :command:`build` command on the command-line means -that command will be run. Including a particular command in configuration files -has no such implication; it only means that if the command is run, the options -for it in the configuration file will apply. (This is also true if you run -other commands that derive values from it.) - -You can find out the complete list of options for any command using the -:option:`--help` option, e.g.:: - - pysetup run build --help - -and you can find out the complete list of global options by using -:option:`--help` without a command:: - - pysetup run --help - -See also the "Reference" section of the "Distributing Python Modules" manual. - -.. XXX no links to the relevant section exist. - - -.. _packaging-building-ext: - -Building extensions: tips and tricks -==================================== - -Whenever possible, Packaging tries to use the configuration information made -available by the Python interpreter used to run `pysetup`. -For example, the same compiler and linker flags used to compile Python will also -be used for compiling extensions. Usually this will work well, but in -complicated situations this might be inappropriate. This section discusses how -to override the usual Packaging behaviour. - - -.. _packaging-tweak-flags: - -Tweaking compiler/linker flags ------------------------------- - -Compiling a Python extension written in C or C++ will sometimes require -specifying custom flags for the compiler and linker in order to use a particular -library or produce a special kind of object code. This is especially true if the -extension hasn't been tested on your platform, or if you're trying to -cross-compile Python. - -.. TODO update to new setup.cfg - -In the most general case, the extension author might have foreseen that -compiling the extensions would be complicated, and provided a :file:`Setup` file -for you to edit. This will likely only be done if the module distribution -contains many separate extension modules, or if they often require elaborate -sets of compiler flags in order to work. - -A :file:`Setup` file, if present, is parsed in order to get a list of extensions -to build. Each line in a :file:`Setup` describes a single module. Lines have -the following structure:: - - module ... [sourcefile ...] [cpparg ...] [library ...] - - -Let's examine each of the fields in turn. - -* *module* is the name of the extension module to be built, and should be a - valid Python identifier. You can't just change this in order to rename a module - (edits to the source code would also be needed), so this should be left alone. - -* *sourcefile* is anything that's likely to be a source code file, at least - judging by the filename. Filenames ending in :file:`.c` are assumed to be - written in C, filenames ending in :file:`.C`, :file:`.cc`, and :file:`.c++` are - assumed to be C++, and filenames ending in :file:`.m` or :file:`.mm` are assumed - to be in Objective C. - -* *cpparg* is an argument for the C preprocessor, and is anything starting with - :option:`-I`, :option:`-D`, :option:`-U` or :option:`-C`. - -* *library* is anything ending in :file:`.a` or beginning with :option:`-l` or - :option:`-L`. - -If a particular platform requires a special library on your platform, you can -add it by editing the :file:`Setup` file and running ``pysetup run build``. -For example, if the module defined by the line :: - - foo foomodule.c - -must be linked with the math library :file:`libm.a` on your platform, simply add -:option:`-lm` to the line:: - - foo foomodule.c -lm - -Arbitrary switches intended for the compiler or the linker can be supplied with -the :option:`-Xcompiler` *arg* and :option:`-Xlinker` *arg* options:: - - foo foomodule.c -Xcompiler -o32 -Xlinker -shared -lm - -The next option after :option:`-Xcompiler` and :option:`-Xlinker` will be -appended to the proper command line, so in the above example the compiler will -be passed the :option:`-o32` option, and the linker will be passed -:option:`-shared`. If a compiler option requires an argument, you'll have to -supply multiple :option:`-Xcompiler` options; for example, to pass ``-x c++`` -the :file:`Setup` file would have to contain ``-Xcompiler -x -Xcompiler c++``. - -Compiler flags can also be supplied through setting the :envvar:`CFLAGS` -environment variable. If set, the contents of :envvar:`CFLAGS` will be added to -the compiler flags specified in the :file:`Setup` file. - - -.. _packaging-non-ms-compilers: - -Using non-Microsoft compilers on Windows ----------------------------------------- - -.. sectionauthor:: Rene Liebscher - - - -Borland/CodeGear C++ -^^^^^^^^^^^^^^^^^^^^ - -This subsection describes the necessary steps to use Packaging with the Borland -C++ compiler version 5.5. First you have to know that Borland's object file -format (OMF) is different from the format used by the Python version you can -download from the Python or ActiveState Web site. (Python is built with -Microsoft Visual C++, which uses COFF as the object file format.) For this -reason, you have to convert Python's library :file:`python25.lib` into the -Borland format. You can do this as follows: - -.. Should we mention that users have to create cfg-files for the compiler? -.. see also http://community.borland.com/article/0,1410,21205,00.html - -:: - - coff2omf python25.lib python25_bcpp.lib - -The :file:`coff2omf` program comes with the Borland compiler. The file -:file:`python25.lib` is in the :file:`Libs` directory of your Python -installation. If your extension uses other libraries (zlib, ...) you have to -convert them too. - -The converted files have to reside in the same directories as the normal -libraries. - -How does Packaging manage to use these libraries with their changed names? If -the extension needs a library (eg. :file:`foo`) Packaging checks first if it -finds a library with suffix :file:`_bcpp` (eg. :file:`foo_bcpp.lib`) and then -uses this library. In the case it doesn't find such a special library it uses -the default name (:file:`foo.lib`.) [#]_ - -To let Packaging compile your extension with Borland, C++ you now have to -type:: - - pysetup run build --compiler bcpp - -If you want to use the Borland C++ compiler as the default, you could specify -this in your personal or system-wide configuration file for Packaging (see -section :ref:`packaging-config-files`.) - - -.. seealso:: - - `C++Builder Compiler `_ - Information about the free C++ compiler from Borland, including links to the - download pages. - - `Creating Python Extensions Using Borland's Free Compiler `_ - Document describing how to use Borland's free command-line C++ compiler to build - Python. - - -GNU C / Cygwin / MinGW -^^^^^^^^^^^^^^^^^^^^^^ - -This section describes the necessary steps to use Packaging with the GNU C/C++ -compilers in their Cygwin and MinGW distributions. [#]_ For a Python interpreter -that was built with Cygwin, everything should work without any of these -following steps. - -Not all extensions can be built with MinGW or Cygwin, but many can. Extensions -most likely to not work are those that use C++ or depend on Microsoft Visual C -extensions. - -To let Packaging compile your extension with Cygwin, you have to type:: - - pysetup run build --compiler=cygwin - -and for Cygwin in no-cygwin mode [#]_ or for MinGW, type:: - - pysetup run build --compiler=mingw32 - -If you want to use any of these options/compilers as default, you should -consider writing it in your personal or system-wide configuration file for -Packaging (see section :ref:`packaging-config-files`.) - -Older Versions of Python and MinGW -"""""""""""""""""""""""""""""""""" -The following instructions only apply if you're using a version of Python -inferior to 2.4.1 with a MinGW inferior to 3.0.0 (with -:file:`binutils-2.13.90-20030111-1`). - -These compilers require some special libraries. This task is more complex than -for Borland's C++, because there is no program to convert the library. First -you have to create a list of symbols which the Python DLL exports. (You can find -a good program for this task at -http://www.emmestech.com/software/pexports-0.43/download_pexports.html). - -.. I don't understand what the next line means. --amk - (inclusive the references on data structures.) - -:: - - pexports python25.dll > python25.def - -The location of an installed :file:`python25.dll` will depend on the -installation options and the version and language of Windows. In a "just for -me" installation, it will appear in the root of the installation directory. In -a shared installation, it will be located in the system directory. - -Then you can create from these information an import library for gcc. :: - - /cygwin/bin/dlltool --dllname python25.dll --def python25.def --output-lib libpython25.a - -The resulting library has to be placed in the same directory as -:file:`python25.lib`. (Should be the :file:`libs` directory under your Python -installation directory.) - -If your extension uses other libraries (zlib,...) you might have to convert -them too. The converted files have to reside in the same directories as the -normal libraries do. - - -.. seealso:: - - `Building Python modules on MS Windows platform with MinGW `_ - Information about building the required libraries for the MinGW - environment. - - -.. rubric:: Footnotes - -.. [#] This also means you could replace all existing COFF-libraries with - OMF-libraries of the same name. - -.. [#] Check http://sources.redhat.com/cygwin/ and http://www.mingw.org/ for - more information. - -.. [#] Then you have no POSIX emulation available, but you also don't need - :file:`cygwin1.dll`. diff --git a/Doc/install/pysetup-config.rst b/Doc/install/pysetup-config.rst deleted file mode 100644 index a473bfe35fa8..000000000000 --- a/Doc/install/pysetup-config.rst +++ /dev/null @@ -1,44 +0,0 @@ -.. _packaging-pysetup-config: - -===================== -Pysetup Configuration -===================== - -Pysetup supports two configuration files: :file:`.pypirc` and :file:`packaging.cfg`. - -.. FIXME integrate with configfile instead of duplicating - -Configuring indexes -------------------- - -You can configure additional indexes in :file:`.pypirc` to be used for index-related -operations. By default, all configured index-servers and package-servers will be used -in an additive fashion. To limit operations to specific indexes, use the :option:`--index` -and :option:`--package-server options`:: - - $ pysetup install --index pypi --package-server django some.project - -Adding indexes to :file:`.pypirc`:: - - [packaging] - index-servers = - pypi - other - - package-servers = - django - - [pypi] - repository: - username: - password: - - [other] - repository: - username: - password: - - [django] - repository: - username: - password: diff --git a/Doc/install/pysetup-servers.rst b/Doc/install/pysetup-servers.rst deleted file mode 100644 index c6106de77d90..000000000000 --- a/Doc/install/pysetup-servers.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _packaging-pysetup-servers: - -=============== -Package Servers -=============== - -Pysetup supports installing Python packages from *Package Servers* in addition -to PyPI indexes and mirrors. - -Package Servers are simple directory listings of Python distributions. Directories -can be served via HTTP or a local file system. This is useful when you want to -dump source distributions in a directory and not worry about the full index structure. - -Serving distributions from Apache ---------------------------------- -:: - - $ mkdir -p /var/www/html/python/distributions - $ cp *.tar.gz /var/www/html/python/distributions/ - - - ServerAdmin webmaster@domain.com - DocumentRoot "/var/www/html/python" - ServerName python.example.org - ErrorLog logs/python.example.org-error.log - CustomLog logs/python.example.org-access.log common - Options Indexes FollowSymLinks MultiViews - DirectoryIndex index.html index.htm - - - Options Indexes FollowSymLinks MultiViews - Order allow,deny - Allow from all - - - -Add the Apache based distribution server to :file:`.pypirc`:: - - [packaging] - package-servers = - apache - - [apache] - repository: http://python.example.org/distributions/ - - -Serving distributions from a file system ----------------------------------------- -:: - - $ mkdir -p /data/python/distributions - $ cp *.tar.gz /data/python/distributions/ - -Add the directory to :file:`.pypirc`:: - - [packaging] - package-servers = - local - - [local] - repository: file:///data/python/distributions/ diff --git a/Doc/install/pysetup.rst b/Doc/install/pysetup.rst deleted file mode 100644 index d472c248e26c..000000000000 --- a/Doc/install/pysetup.rst +++ /dev/null @@ -1,164 +0,0 @@ -.. _packaging-pysetup: - -================ -Pysetup Tutorial -================ - -Getting started ---------------- - -Pysetup is a simple script that supports the following features: - -- install, remove, list, and verify Python packages; -- search for available packages on PyPI or any *Simple Index*; -- verify installed packages (md5sum, installed files, version). - - -Finding out what's installed ----------------------------- - -Pysetup makes it easy to find out what Python packages are installed:: - - $ pysetup list virtualenv - 'virtualenv' 1.6 at '/opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info' - - $ pysetup list - 'pyverify' 0.8.1 at '/opt/python3.3/lib/python3.3/site-packages/pyverify-0.8.1.dist-info' - 'virtualenv' 1.6 at '/opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info' - ... - - -Installing a distribution -------------------------- - -Pysetup can install a Python project from the following sources: - -- PyPI and Simple Indexes; -- source directories containing a valid :file:`setup.py` or :file:`setup.cfg`; -- distribution source archives (:file:`project-1.0.tar.gz`, :file:`project-1.0.zip`); -- HTTP (http://host/packages/project-1.0.tar.gz). - - -Installing from PyPI and Simple Indexes:: - - $ pysetup install project - $ pysetup install project==1.0 - -Installing from a distribution source archive:: - - $ pysetup install project-1.0.tar.gz - -Installing from a source directory containing a valid :file:`setup.py` or -:file:`setup.cfg`:: - - $ cd path/to/source/directory - $ pysetup install - - $ pysetup install path/to/source/directory - -Installing from HTTP:: - - $ pysetup install http://host/packages/project-1.0.tar.gz - - -Retrieving metadata -------------------- - -You can gather metadata from two sources, a project's source directory or an -installed distribution. The `pysetup metadata` command can retrieve one or -more metadata fields using the `-f` option and a metadata field as the -argument. :: - - $ pysetup metadata virtualenv -f version -f name - Version: - 1.6 - Name: - virtualenv - - $ pysetup metadata virtualenv - Metadata-Version: - 1.0 - Name: - virtualenv - Version: - 1.6 - Platform: - UNKNOWN - Summary: - Virtual Python Environment builder - ... - -.. seealso:: - - There are three metadata versions, 1.0, 1.1, and 1.2. The following PEPs - describe specifics of the field names, and their semantics and usage. 1.0 - :PEP:`241`, 1.1 :PEP:`314`, and 1.2 :PEP:`345` - - -Removing a distribution ------------------------ - -You can remove one or more installed distributions using the `pysetup remove` -command:: - - $ pysetup remove virtualenv - removing 'virtualenv': - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/dependency_links.txt - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/entry_points.txt - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/not-zip-safe - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/PKG-INFO - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/SOURCES.txt - /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info/top_level.txt - Proceed (y/n)? y - success: removed 6 files and 1 dirs - -The optional '-y' argument auto confirms, skipping the conformation prompt:: - - $ pysetup remove virtualenv -y - - -Getting help ------------- - -All pysetup actions take the `-h` and `--help` options which prints the commands -help string to stdout. :: - - $ pysetup remove -h - Usage: pysetup remove dist [-y] - or: pysetup remove --help - - Uninstall a Python package. - - positional arguments: - dist installed distribution name - - optional arguments: - -y auto confirm package removal - -Getting a list of all pysetup actions and global options:: - - $ pysetup --help - Usage: pysetup [options] action [action_options] - - Actions: - run: Run one or several commands - metadata: Display the metadata of a project - install: Install a project - remove: Remove a project - search: Search for a project in the indexes - list: List installed projects - graph: Display a graph - create: Create a project - generate-setup: Generate a backward-compatible setup.py - - To get more help on an action, use: - - pysetup action --help - - Global options: - --verbose (-v) run verbosely (default) - --quiet (-q) run quietly (turns verbosity off) - --dry-run (-n) don't actually do anything - --help (-h) show detailed help message - --no-user-cfg ignore pydistutils.cfg in your home directory - --version Display the version diff --git a/Doc/library/distutils.rst b/Doc/library/distutils.rst index 53a69aecd7c4..11a29493a5ad 100644 --- a/Doc/library/distutils.rst +++ b/Doc/library/distutils.rst @@ -12,10 +12,6 @@ additional modules into a Python installation. The new modules may be either 100%-pure Python, or may be extension modules written in C, or may be collections of Python packages which include modules coded in both Python and C. -.. deprecated:: 3.3 - :mod:`packaging` replaces Distutils. See :ref:`packaging-index` and - :ref:`packaging-install-index`. - User documentation and API reference are provided in another document: @@ -27,11 +23,3 @@ User documentation and API reference are provided in another document: easily installed into an existing Python installation. If also contains instructions for end-users wanting to install a distutils-based package, :ref:`install-index`. - - -.. trick to silence a Sphinx warning - -.. toctree:: - :hidden: - - ../distutils/index diff --git a/Doc/library/packaging-misc.rst b/Doc/library/packaging-misc.rst deleted file mode 100644 index 5e562473e2ed..000000000000 --- a/Doc/library/packaging-misc.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. temporary file for modules that don't need a dedicated file yet - -:mod:`packaging.errors` --- Packaging exceptions -================================================ - -.. module:: packaging.errors - :synopsis: Packaging exceptions. - - -Provides exceptions used by the Packaging modules. Note that Packaging modules -may raise standard exceptions; in particular, SystemExit is usually raised for -errors that are obviously the end-user's fault (e.g. bad command-line arguments). - -This module is safe to use in ``from ... import *`` mode; it only exports -symbols whose names start with ``Packaging`` and end with ``Error``. - - -:mod:`packaging.manifest` --- The Manifest class -================================================ - -.. module:: packaging.manifest - :synopsis: The Manifest class, used for poking about the file system and - building lists of files. - - -This module provides the :class:`Manifest` class, used for poking about the -filesystem and building lists of files. diff --git a/Doc/library/packaging.command.rst b/Doc/library/packaging.command.rst deleted file mode 100644 index 6a85351728f3..000000000000 --- a/Doc/library/packaging.command.rst +++ /dev/null @@ -1,111 +0,0 @@ -:mod:`packaging.command` --- Standard Packaging commands -======================================================== - -.. module:: packaging.command - :synopsis: Standard packaging commands. - - -This subpackage contains one module for each standard Packaging command, such as -:command:`build` or :command:`upload`. Each command is implemented as a -separate module, with the command name as the name of the module and of the -class defined therein. - - - -:mod:`packaging.command.cmd` --- Abstract base class for Packaging commands -=========================================================================== - -.. module:: packaging.command.cmd - :synopsis: Abstract base class for commands. - - -This module supplies the abstract base class :class:`Command`. This class is -subclassed by the modules in the packaging.command subpackage. - - -.. class:: Command(dist) - - 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 :meth:`initialize_options` and defined (given their final values) in - :meth:`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 :meth:`finalize_options`. The body - of the subroutine, where it does all its work based on the values of its - options, is the :meth:`run` method, which must also be implemented by every - command class. - - The class constructor takes a single argument *dist*, a - :class:`~packaging.dist.Distribution` instance. - - -Creating a new Packaging command --------------------------------- - -This section outlines the steps to create a new Packaging command. - -.. XXX the following paragraph is focused on the stdlib; expand it to document - how to write and register a command in third-party projects - -A new command lives in a module in the :mod:`packaging.command` package. There -is a sample template in that directory called :file:`command_template`. Copy -this file to a new module with the same name as the new command you're -implementing. This module should implement a class with the same name as the -module (and the command). So, for instance, to create the command -``peel_banana`` (so that users can run ``setup.py peel_banana``), you'd copy -:file:`command_template` to :file:`packaging/command/peel_banana.py`, then edit -it so that it's implementing the class :class:`peel_banana`, a subclass of -:class:`Command`. It must define the following methods: - -.. method:: Command.initialize_options() - - 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, :meth:`initialize_options` - implementations are just a bunch of ``self.foo = None`` assignments. - - -.. method:: Command.finalize_options() - - Set final values for all the options that this command supports. This is - always called as late as possible, i.e. 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 :meth:`initialize_options`. - - -.. method:: Command.run() - - A command's raison d'etre: carry out the action it exists to perform, - controlled by the options initialized in :meth:`initialize_options`, - customized by other commands, the setup script, the command line, and config - files, and finalized in :meth:`finalize_options`. All terminal output and - filesystem interaction should be done by :meth:`run`. - - -Command classes may define this attribute: - - -.. attribute:: Command.sub_commands - - *sub_commands* formalizes the notion of a "family" of commands, - e.g. ``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 2-tuples ``(command_name, - predicate)``, with *command_name* a string and *predicate* a function, a - string or ``None``. *predicate* is a method of the parent command that - determines whether the corresponding command is applicable in the current - situation. (E.g. ``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 methods of the class, so they must already have been - defined. The canonical example is the :command:`install_dist` command. - -.. XXX document how to add a custom command to another one's subcommands diff --git a/Doc/library/packaging.compiler.rst b/Doc/library/packaging.compiler.rst deleted file mode 100644 index ecf641e39190..000000000000 --- a/Doc/library/packaging.compiler.rst +++ /dev/null @@ -1,681 +0,0 @@ -:mod:`packaging.compiler` --- Compiler classes -============================================== - -.. module:: packaging.compiler - :synopsis: Compiler classes to build C/C++ extensions or libraries. - - -This subpackage contains an abstract base class representing a compiler and -concrete implementations for common compilers. The compiler classes should not -be instantiated directly, but created using the :func:`new_compiler` factory -function. Compiler types provided by Packaging are listed in -:ref:`packaging-standard-compilers`. - - -Public functions ----------------- - -.. function:: new_compiler(plat=None, compiler=None, dry_run=False, force=False) - - Factory function to generate an instance of some - :class:`~.ccompiler.CCompiler` subclass for the requested platform or - compiler type. - - If no argument is given for *plat* and *compiler*, the default compiler type - for the platform (:attr:`os.name`) will be used: ``'unix'`` for Unix and - Mac OS X, ``'msvc'`` for Windows. - - If *plat* is given, it must be one of ``'posix'``, ``'darwin'`` or ``'nt'``. - An invalid value will not raise an exception but use the default compiler - type for the current platform. - - .. XXX errors should never pass silently; this behavior is particularly - harmful when a compiler type is given as first argument - - If *compiler* is given, *plat* will be ignored, allowing you to get for - example a ``'unix'`` compiler object under Windows or an ``'msvc'`` compiler - under Unix. However, not all compiler types can be instantiated on every - platform. - - -.. function:: customize_compiler(compiler) - - Do any platform-specific customization of a CCompiler instance. Mainly - needed on Unix to plug in the information that varies across Unices and is - stored in CPython's Makefile. - - -.. function:: gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries) - - Generate linker options for searching library directories and linking with - specific libraries. *libraries* and *library_dirs* are, respectively, lists - of library names (not filenames!) and search directories. Returns a list of - command-line options suitable for use with some compiler (depending on the - two format strings passed in). - - -.. function:: gen_preprocess_options(macros, include_dirs) - - Generate C preprocessor options (:option:`-D`, :option:`-U`, :option:`-I`) as - used by at least two types of compilers: the typical Unix compiler and Visual - C++. *macros* is the usual thing, a list of 1- or 2-tuples, where ``(name,)`` - means undefine (:option:`-U`) macro *name*, and ``(name, value)`` means - define (:option:`-D`) macro *name* to *value*. *include_dirs* is just a list - of directory names to be added to the header file search path (:option:`-I`). - Returns a list of command-line options suitable for either Unix compilers or - Visual C++. - - -.. function:: get_default_compiler(osname, platform) - - Determine the default compiler to use for the given platform. - - *osname* should be one of the standard Python OS names (i.e. the ones - returned by ``os.name``) and *platform* the common value returned by - ``sys.platform`` for the platform in question. - - The default values are ``os.name`` and ``sys.platform``. - - -.. function:: set_compiler(location) - - Add or change a compiler - - -.. function:: show_compilers() - - Print list of available compilers (used by the :option:`--help-compiler` - options to :command:`build`, :command:`build_ext`, :command:`build_clib`). - - -.. _packaging-standard-compilers: - -Standard compilers ------------------- - -Concrete subclasses of :class:`~.ccompiler.CCompiler` are provided in submodules -of the :mod:`packaging.compiler` package. You do not need to import them, using -:func:`new_compiler` is the public API to use. This table documents the -standard compilers; be aware that they can be replaced by other classes on your -platform. - -=============== ======================================================== ======= -name description notes -=============== ======================================================== ======= -``'unix'`` typical Unix-style command-line C compiler [#]_ -``'msvc'`` Microsoft compiler [#]_ -``'bcpp'`` Borland C++ compiler -``'cygwin'`` Cygwin compiler (Windows port of GCC) -``'mingw32'`` Mingw32 port of GCC (same as Cygwin in no-Cygwin mode) -=============== ======================================================== ======= - - -.. [#] The Unix compiler class assumes this behavior: - - * macros defined with :option:`-Dname[=value]` - - * macros undefined with :option:`-Uname` - - * include search directories specified with :option:`-Idir` - - * libraries specified with :option:`-llib` - - * library search directories specified with :option:`-Ldir` - - * compile handled by :program:`cc` (or similar) executable with - :option:`-c` option: compiles :file:`.c` to :file:`.o` - - * link static library handled by :program:`ar` command (possibly with - :program:`ranlib`) - - * link shared library handled by :program:`cc` :option:`-shared` - - -.. [#] On Windows, extension modules typically need to be compiled with the same - compiler that was used to compile CPython (for example Microsoft Visual - Studio .NET 2003 for CPython 2.4 and 2.5). The AMD64 and Itanium - binaries are created using the Platform SDK. - - Under the hood, there are actually two different subclasses of - :class:`~.ccompiler.CCompiler` defined: one is compatible with MSVC 2005 - and 2008, the other works with older versions. This should not be a - concern for regular use of the functions in this module. - - Packaging will normally choose the right compiler, linker etc. on its - own. To override this choice, the environment variables - *DISTUTILS_USE_SDK* and *MSSdk* must be both set. *MSSdk* indicates that - the current environment has been setup by the SDK's ``SetEnv.Cmd`` - script, or that the environment variables had been registered when the - SDK was installed; *DISTUTILS_USE_SDK* indicates that the user has made - an explicit choice to override the compiler selection done by Packaging. - - .. TODO document the envvars in Doc/using and the man page - - -:mod:`packaging.compiler.ccompiler` --- CCompiler base class -============================================================ - -.. module:: packaging.compiler.ccompiler - :synopsis: Abstract CCompiler class. - - -This module provides the abstract base class for the :class:`CCompiler` -classes. A :class:`CCompiler` instance can be used for all the compile and -link steps needed to build a single project. Methods are provided to set -options for the compiler --- macro definitions, include directories, link path, -libraries and the like. - -.. class:: CCompiler(dry_run=False, force=False) - - The abstract base class :class:`CCompiler` defines the interface that must be - implemented by real compiler classes. The class also has some utility - methods used by several compiler classes. - - The basic idea behind a compiler abstraction class is that each instance can - be used for all the compile/link steps in building a single project. Thus, - attributes common to all of those compile and link steps --- include - directories, macros to define, libraries to link against, etc. --- are - attributes of the compiler instance. To allow for variability in how - individual files are treated, most of those attributes may be varied on a - per-compilation or per-link basis. - - The constructor for each subclass creates an instance of the Compiler object. - Flags are *dry_run* (don't actually execute - the steps) and *force* (rebuild everything, regardless of dependencies). All - of these flags default to ``False`` (off). Note that you probably don't want to - instantiate :class:`CCompiler` or one of its subclasses directly - use the - :func:`new_compiler` factory function instead. - - The following methods allow you to manually alter compiler options for the - instance of the Compiler class. - - - .. method:: CCompiler.add_include_dir(dir) - - Add *dir* to the list of directories that will be searched for header - files. The compiler is instructed to search directories in the order in - which they are supplied by successive calls to :meth:`add_include_dir`. - - - .. method:: CCompiler.set_include_dirs(dirs) - - Set the list of directories that will be searched to *dirs* (a list of - strings). Overrides any preceding calls to :meth:`add_include_dir`; - subsequent calls to :meth:`add_include_dir` add to the list passed to - :meth:`set_include_dirs`. This does not affect any list of standard - include directories that the compiler may search by default. - - - .. method:: CCompiler.add_library(libname) - - Add *libname* to the list of libraries that will be included in all links - driven by this compiler object. Note that *libname* should *not* be the - name of a file containing a library, but the name of the library itself: - the actual filename will be inferred by the linker, the compiler, or the - compiler class (depending on the platform). - - The linker will be instructed to link against libraries in the order they - were supplied to :meth:`add_library` and/or :meth:`set_libraries`. It is - perfectly valid to duplicate library names; the linker will be instructed - to link against libraries as many times as they are mentioned. - - - .. method:: CCompiler.set_libraries(libnames) - - Set the list of libraries to be included in all links driven by this - compiler object to *libnames* (a list of strings). This does not affect - any standard system libraries that the linker may include by default. - - - .. method:: CCompiler.add_library_dir(dir) - - Add *dir* to the list of directories that will be searched for libraries - specified to :meth:`add_library` and :meth:`set_libraries`. The linker - will be instructed to search for libraries in the order they are supplied - to :meth:`add_library_dir` and/or :meth:`set_library_dirs`. - - - .. method:: CCompiler.set_library_dirs(dirs) - - Set the list of library search directories to *dirs* (a list of strings). - This does not affect any standard library search path that the linker may - search by default. - - - .. method:: CCompiler.add_runtime_library_dir(dir) - - Add *dir* to the list of directories that will be searched for shared - libraries at runtime. - - - .. method:: CCompiler.set_runtime_library_dirs(dirs) - - Set the list of directories to search for shared libraries at runtime to - *dirs* (a list of strings). This does not affect any standard search path - that the runtime linker may search by default. - - - .. method:: CCompiler.define_macro(name, value=None) - - Define a preprocessor macro for all compilations driven by this compiler - object. The optional parameter *value* should be a string; if it is not - supplied, then the macro will be defined without an explicit value and the - exact outcome depends on the compiler used (XXX true? does ANSI say - anything about this?) - - - .. method:: CCompiler.undefine_macro(name) - - Undefine a preprocessor macro for all compilations driven by this compiler - object. If the same macro is defined by :meth:`define_macro` and - undefined by :meth:`undefine_macro` the last call takes precedence - (including multiple redefinitions or undefinitions). If the macro is - redefined/undefined on a per-compilation basis (i.e. in the call to - :meth:`compile`), then that takes precedence. - - - .. method:: CCompiler.add_link_object(object) - - Add *object* to the list of object files (or analogues, such as explicitly - named library files or the output of "resource compilers") to be included - in every link driven by this compiler object. - - - .. method:: CCompiler.set_link_objects(objects) - - Set the list of object files (or analogues) to be included in every link - to *objects*. This does not affect any standard object files that the - linker may include by default (such as system libraries). - - The following methods implement methods for autodetection of compiler - options, providing some functionality similar to GNU :program:`autoconf`. - - - .. method:: CCompiler.detect_language(sources) - - Detect the language of a given file, or list of files. Uses the instance - attributes :attr:`language_map` (a dictionary), and :attr:`language_order` - (a list) to do the job. - - - .. method:: CCompiler.find_library_file(dirs, lib, debug=0) - - Search the specified list of directories for a static or shared library file - *lib* and return the full path to that file. If *debug* is true, look for a - debugging version (if that makes sense on the current platform). Return - ``None`` if *lib* wasn't found in any of the specified directories. - - - .. method:: CCompiler.has_function(funcname, includes=None, include_dirs=None, libraries=None, library_dirs=None) - - Return a boolean indicating whether *funcname* is supported on the current - platform. The optional arguments can be used to augment the compilation - environment by providing additional include files and paths and libraries and - paths. - - - .. method:: CCompiler.library_dir_option(dir) - - Return the compiler option to add *dir* to the list of directories searched for - libraries. - - - .. method:: CCompiler.library_option(lib) - - Return the compiler option to add *dir* to the list of libraries linked into the - shared library or executable. - - - .. method:: CCompiler.runtime_library_dir_option(dir) - - Return the compiler option to add *dir* to the list of directories searched for - runtime libraries. - - - .. method:: CCompiler.set_executables(**args) - - Define the executables (and options for them) that will be run to perform the - various stages of compilation. The exact set of executables that may be - specified here depends on the compiler class (via the 'executables' class - attribute), but most will have: - - +--------------+------------------------------------------+ - | attribute | description | - +==============+==========================================+ - | *compiler* | the C/C++ compiler | - +--------------+------------------------------------------+ - | *linker_so* | linker used to create shared objects and | - | | libraries | - +--------------+------------------------------------------+ - | *linker_exe* | linker used to create binary executables | - +--------------+------------------------------------------+ - | *archiver* | static library creator | - +--------------+------------------------------------------+ - - On platforms with a command line (Unix, DOS/Windows), each of these is a string - that will be split into executable name and (optional) list of arguments. - (Splitting the string is done similarly to how Unix shells operate: words are - delimited by spaces, but quotes and backslashes can override this. See - :func:`packaging.util.split_quoted`.) - - The following methods invoke stages in the build process. - - - .. method:: CCompiler.compile(sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None) - - Compile one or more source files. Generates object files (e.g. transforms a - :file:`.c` file to a :file:`.o` file.) - - *sources* must be a list of filenames, most likely C/C++ files, but in reality - anything that can be handled by a particular compiler and compiler class (e.g. - an ``'msvc'`` compiler can handle resource files in *sources*). Return a list of - object filenames, one per source filename in *sources*. Depending on the - implementation, not all source files will necessarily be compiled, but all - corresponding object filenames will be returned. - - If *output_dir* is given, object files will be put under it, while retaining - their original path component. That is, :file:`foo/bar.c` normally compiles to - :file:`foo/bar.o` (for a Unix implementation); if *output_dir* is *build*, then - it would compile to :file:`build/foo/bar.o`. - - *macros*, if given, must be a list of macro definitions. A macro definition is - either a ``(name, value)`` 2-tuple or a ``(name,)`` 1-tuple. The former defines - a macro; if the value is ``None``, the macro is defined without an explicit - value. The 1-tuple case undefines a macro. Later - definitions/redefinitions/undefinitions take precedence. - - *include_dirs*, if given, must be a list of strings, the directories to add to - the default include file search path for this compilation only. - - *debug* is a boolean; if true, the compiler will be instructed to output debug - symbols in (or alongside) the object file(s). - - *extra_preargs* and *extra_postargs* are implementation-dependent. On platforms - that have the notion of a command line (e.g. Unix, DOS/Windows), they are most - likely lists of strings: extra command-line arguments to prepend/append to the - compiler command line. On other platforms, consult the implementation class - documentation. In any event, they are intended as an escape hatch for those - occasions when the abstract compiler framework doesn't cut the mustard. - - *depends*, if given, is a list of filenames that all targets depend on. If a - source file is older than any file in depends, then the source file will be - recompiled. This supports dependency tracking, but only at a coarse - granularity. - - Raises :exc:`CompileError` on failure. - - - .. method:: CCompiler.create_static_lib(objects, output_libname, output_dir=None, debug=0, target_lang=None) - - Link a bunch of stuff together to create a static library file. The "bunch of - stuff" consists of the list of object files supplied as *objects*, the extra - object files supplied to :meth:`add_link_object` and/or - :meth:`set_link_objects`, the libraries supplied to :meth:`add_library` and/or - :meth:`set_libraries`, and the libraries supplied as *libraries* (if any). - - *output_libname* should be a library name, not a filename; the filename will be - inferred from the library name. *output_dir* is the directory where the library - file will be put. XXX defaults to what? - - *debug* is a boolean; if true, debugging information will be included in the - library (note that on most platforms, it is the compile step where this matters: - the *debug* flag is included here just for consistency). - - *target_lang* is the target language for which the given objects are being - compiled. This allows specific linkage time treatment of certain languages. - - Raises :exc:`LibError` on failure. - - - .. method:: CCompiler.link(target_desc, objects, output_filename, output_dir=None, libraries=None, library_dirs=None, runtime_library_dirs=None, export_symbols=None, debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, target_lang=None) - - Link a bunch of stuff together to create an executable or shared library file. - - The "bunch of stuff" consists of the list of object files supplied as *objects*. - *output_filename* should be a filename. If *output_dir* is supplied, - *output_filename* is relative to it (i.e. *output_filename* can provide - directory components if needed). - - *libraries* is a list of libraries to link against. These are library names, - not filenames, since they're translated into filenames in a platform-specific - way (e.g. *foo* becomes :file:`libfoo.a` on Unix and :file:`foo.lib` on - DOS/Windows). However, they can include a directory component, which means the - linker will look in that specific directory rather than searching all the normal - locations. - - *library_dirs*, if supplied, should be a list of directories to search for - libraries that were specified as bare library names (i.e. no directory - component). These are on top of the system default and those supplied to - :meth:`add_library_dir` and/or :meth:`set_library_dirs`. *runtime_library_dirs* - is a list of directories that will be embedded into the shared library and used - to search for other shared libraries that \*it\* depends on at run-time. (This - may only be relevant on Unix.) - - *export_symbols* is a list of symbols that the shared library will export. - (This appears to be relevant only on Windows.) - - *debug* is as for :meth:`compile` and :meth:`create_static_lib`, with the - slight distinction that it actually matters on most platforms (as opposed to - :meth:`create_static_lib`, which includes a *debug* flag mostly for form's - sake). - - *extra_preargs* and *extra_postargs* are as for :meth:`compile` (except of - course that they supply command-line arguments for the particular linker being - used). - - *target_lang* is the target language for which the given objects are being - compiled. This allows specific linkage time treatment of certain languages. - - Raises :exc:`LinkError` on failure. - - - .. method:: CCompiler.link_executable(objects, output_progname, output_dir=None, libraries=None, library_dirs=None, runtime_library_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, target_lang=None) - - Link an executable. *output_progname* is the name of the file executable, while - *objects* are a list of object filenames to link in. Other arguments are as for - the :meth:`link` method. - - - .. method:: CCompiler.link_shared_lib(objects, output_libname, output_dir=None, libraries=None, library_dirs=None, runtime_library_dirs=None, export_symbols=None, debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, target_lang=None) - - Link a shared library. *output_libname* is the name of the output library, - while *objects* is a list of object filenames to link in. Other arguments are - as for the :meth:`link` method. - - - .. method:: CCompiler.link_shared_object(objects, output_filename, output_dir=None, libraries=None, library_dirs=None, runtime_library_dirs=None, export_symbols=None, debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, target_lang=None) - - Link a shared object. *output_filename* is the name of the shared object that - will be created, while *objects* is a list of object filenames to link in. - Other arguments are as for the :meth:`link` method. - - - .. method:: CCompiler.preprocess(source, output_file=None, macros=None, include_dirs=None, extra_preargs=None, extra_postargs=None) - - Preprocess a single C/C++ source file, named in *source*. Output will be written - to file named *output_file*, or *stdout* if *output_file* not supplied. - *macros* is a list of macro definitions as for :meth:`compile`, which will - augment the macros set with :meth:`define_macro` and :meth:`undefine_macro`. - *include_dirs* is a list of directory names that will be added to the default - list, in the same way as :meth:`add_include_dir`. - - Raises :exc:`PreprocessError` on failure. - - The following utility methods are defined by the :class:`CCompiler` class, for - use by the various concrete subclasses. - - - .. method:: CCompiler.executable_filename(basename, strip_dir=0, output_dir='') - - Returns the filename of the executable for the given *basename*. Typically for - non-Windows platforms this is the same as the basename, while Windows will get - a :file:`.exe` added. - - - .. method:: CCompiler.library_filename(libname, lib_type='static', strip_dir=0, output_dir='') - - Returns the filename for the given library name on the current platform. On Unix - a library with *lib_type* of ``'static'`` will typically be of the form - :file:`liblibname.a`, while a *lib_type* of ``'dynamic'`` will be of the form - :file:`liblibname.so`. - - - .. method:: CCompiler.object_filenames(source_filenames, strip_dir=0, output_dir='') - - Returns the name of the object files for the given source files. - *source_filenames* should be a list of filenames. - - - .. method:: CCompiler.shared_object_filename(basename, strip_dir=0, output_dir='') - - Returns the name of a shared object file for the given file name *basename*. - - - .. method:: CCompiler.execute(func, args, msg=None, level=1) - - Invokes :func:`packaging.util.execute` This method invokes a Python function - *func* with the given arguments *args*, after logging and taking into account - the *dry_run* flag. XXX see also. - - - .. method:: CCompiler.spawn(cmd) - - Invokes :func:`packaging.util.spawn`. This invokes an external process to run - the given command. XXX see also. - - - .. method:: CCompiler.mkpath(name, mode=511) - - Invokes :func:`packaging.dir_util.mkpath`. This creates a directory and any - missing ancestor directories. XXX see also. - - - .. method:: CCompiler.move_file(src, dst) - - Invokes :meth:`packaging.file_util.move_file`. Renames *src* to *dst*. XXX see - also. - - -:mod:`packaging.compiler.extension` --- The Extension class -=========================================================== - -.. module:: packaging.compiler.extension - :synopsis: Class used to represent C/C++ extension modules. - - -This module provides the :class:`Extension` class, used to represent C/C++ -extension modules. - -.. class:: Extension - - The Extension class describes a single C or C++ extension module. It accepts - the following keyword arguments in its constructor: - - +------------------------+--------------------------------+---------------------------+ - | argument name | value | type | - +========================+================================+===========================+ - | *name* | the full name of the | string | - | | extension, including any | | - | | packages --- i.e. *not* a | | - | | filename or pathname, but | | - | | Python dotted name | | - +------------------------+--------------------------------+---------------------------+ - | *sources* | list of source filenames, | list of strings | - | | 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 | | - | | :command:`build_ext` command | | - | | as source for a Python | | - | | extension. | | - +------------------------+--------------------------------+---------------------------+ - | *include_dirs* | list of directories to search | list of strings | - | | for C/C++ header files (in | | - | | Unix form for portability) | | - +------------------------+--------------------------------+---------------------------+ - | *define_macros* | list of macros to define; each | list of tuples | - | | macro is defined using a | | - | | 2-tuple ``(name, value)``, | | - | | 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 :option:`-DFOO` | | - | | on Unix C compiler command | | - | | line) | | - +------------------------+--------------------------------+---------------------------+ - | *undef_macros* | list of macros to undefine | list of strings | - | | explicitly | | - +------------------------+--------------------------------+---------------------------+ - | *library_dirs* | list of directories to search | list of strings | - | | for C/C++ libraries at link | | - | | time | | - +------------------------+--------------------------------+---------------------------+ - | *libraries* | list of library names (not | list of strings | - | | filenames or paths) to link | | - | | against | | - +------------------------+--------------------------------+---------------------------+ - | *runtime_library_dirs* | list of directories to search | list of strings | - | | for C/C++ libraries at run | | - | | time (for shared extensions, | | - | | this is when the extension is | | - | | loaded) | | - +------------------------+--------------------------------+---------------------------+ - | *extra_objects* | list of extra files to link | list of strings | - | | with (e.g. object files not | | - | | implied by 'sources', static | | - | | library that must be | | - | | explicitly specified, binary | | - | | resource files, etc.) | | - +------------------------+--------------------------------+---------------------------+ - | *extra_compile_args* | any extra platform- and | list of strings | - | | compiler-specific information | | - | | to use when compiling the | | - | | source files in 'sources'. For | | - | | platforms and compilers where | | - | | a command line makes sense, | | - | | this is typically a list of | | - | | command-line arguments, but | | - | | for other platforms it could | | - | | be anything. | | - +------------------------+--------------------------------+---------------------------+ - | *extra_link_args* | any extra platform- and | list of strings | - | | 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* | list of symbols to be exported | list of strings | - | | 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. | | - +------------------------+--------------------------------+---------------------------+ - | *depends* | list of files that the | list of strings | - | | extension depends on | | - +------------------------+--------------------------------+---------------------------+ - | *language* | extension language (i.e. | string | - | | ``'c'``, ``'c++'``, | | - | | ``'objc'``). Will be detected | | - | | from the source extensions if | | - | | not provided. | | - +------------------------+--------------------------------+---------------------------+ - | *optional* | specifies that a build failure | boolean | - | | in the extension should not | | - | | abort the build process, but | | - | | simply skip the extension. | | - +------------------------+--------------------------------+---------------------------+ - -To distribute extension modules that live in a package (e.g. ``package.ext``), -you need to create a :file:`{package}/__init__.py` file to let Python recognize -and import your module. diff --git a/Doc/library/packaging.database.rst b/Doc/library/packaging.database.rst deleted file mode 100644 index 9d750f00d961..000000000000 --- a/Doc/library/packaging.database.rst +++ /dev/null @@ -1,345 +0,0 @@ -:mod:`packaging.database` --- Database of installed distributions -================================================================= - -.. module:: packaging.database - :synopsis: Functions to query and manipulate installed distributions. - - -This module provides an implementation of :PEP:`376`. It was originally -intended to land in :mod:`pkgutil`, but with the inclusion of Packaging in the -standard library, it was thought best to include it in a submodule of -:mod:`packaging`, leaving :mod:`pkgutil` to deal with imports. - -Installed Python distributions are represented by instances of -:class:`Distribution`, or :class:`EggInfoDistribution` for legacy egg formats. -Most functions also provide an extra argument ``use_egg_info`` to take legacy -distributions into account. - -For the purpose of this module, "installed" means that the distribution's -:file:`.dist-info`, :file:`.egg-info` or :file:`egg` directory or file is found -on :data:`sys.path`. For example, if the parent directory of a -:file:`dist-info` directory is added to :envvar:`PYTHONPATH`, then it will be -available in the database. - -Classes representing installed distributions --------------------------------------------- - -.. class:: Distribution(path) - - Class representing an installed distribution. It is different from - :class:`packaging.dist.Distribution` which holds the list of files, the - metadata and options during the run of a Packaging command. - - Instantiate with the *path* to a ``.dist-info`` directory. Instances can be - compared and sorted. Other available methods are: - - .. XXX describe how comparison works - - .. method:: get_distinfo_file(path, binary=False) - - Return a read-only file object for a file located at - :file:`{project}-{version}.dist-info/{path}`. *path* should be a - ``'/'``-separated path relative to the ``.dist-info`` directory or an - absolute path; if it is an absolute path and doesn't start with the path - to the :file:`.dist-info` directory, a :class:`PackagingError` is raised. - - If *binary* is ``True``, the file is opened in binary mode. - - .. method:: get_resource_path(relative_path) - - .. TODO - - .. method:: list_distinfo_files(local=False) - - Return an iterator over all files located in the :file:`.dist-info` - directory. If *local* is ``True``, each returned path is transformed into - a local absolute path, otherwise the raw value found in the :file:`RECORD` - file is returned. - - .. method:: list_installed_files(local=False) - - Iterate over the files installed with the distribution and registered in - the :file:`RECORD` file and yield 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 is returned. - - A local absolute path is an absolute path in which occurrences of ``'/'`` - have been replaced by :data:`os.sep`. - - .. method:: uses(path) - - Check whether *path* was installed by this distribution (i.e. if the path - is present in the :file:`RECORD` file). *path* can be a local absolute - path or a relative ``'/'``-separated path. Returns a boolean. - - Available attributes: - - .. attribute:: metadata - - Instance of :class:`packaging.metadata.Metadata` filled with the contents - of the :file:`{project}-{version}.dist-info/METADATA` file. - - .. attribute:: name - - Shortcut for ``metadata['Name']``. - - .. attribute:: version - - Shortcut for ``metadata['Version']``. - - .. attribute:: requested - - Boolean indicating whether this distribution was requested by the user of - automatically installed as a dependency. - - -.. class:: EggInfoDistribution(path) - - Class representing a legacy distribution. It is compatible with distutils' - and setuptools' :file:`.egg-info` and :file:`.egg` files and directories. - - .. FIXME should be named EggDistribution - - Instantiate with the *path* to an egg file or directory. Instances can be - compared and sorted. Other available methods are: - - .. method:: list_installed_files(local=False) - - .. method:: uses(path) - - Available attributes: - - .. attribute:: metadata - - Instance of :class:`packaging.metadata.Metadata` filled with the contents - of the :file:`{project-version}.egg-info/PKG-INFO` or - :file:`{project-version}.egg` file. - - .. attribute:: name - - Shortcut for ``metadata['Name']``. - - .. attribute:: version - - Shortcut for ``metadata['Version']``. - - -Functions to work with the database ------------------------------------ - -.. function:: get_distribution(name, use_egg_info=False, paths=None) - - Return an instance of :class:`Distribution` or :class:`EggInfoDistribution` - for the first installed distribution matching *name*. Egg distributions are - considered only if *use_egg_info* is true; if both a dist-info and an egg - file are found, the dist-info prevails. The directories to be searched are - given in *paths*, which defaults to :data:`sys.path`. Returns ``None`` if no - matching distribution is found. - - .. FIXME param should be named use_egg - - -.. function:: get_distributions(use_egg_info=False, paths=None) - - Return an iterator of :class:`Distribution` instances for all installed - distributions found in *paths* (defaults to :data:`sys.path`). If - *use_egg_info* is true, also return instances of :class:`EggInfoDistribution` - for legacy distributions found. - - -.. function:: get_file_users(path) - - Return an iterator over all distributions using *path*, a local absolute path - or a relative ``'/'``-separated path. - - .. XXX does this work with prefixes or full file path only? - - -.. function:: obsoletes_distribution(name, version=None, use_egg_info=False) - - Return an iterator over all distributions that declare they obsolete *name*. - *version* is an optional argument to match only specific releases (see - :mod:`packaging.version`). If *use_egg_info* is true, legacy egg - distributions will be considered as well. - - -.. function:: provides_distribution(name, version=None, use_egg_info=False) - - Return an iterator over all distributions that declare they provide *name*. - *version* is an optional argument to match only specific releases (see - :mod:`packaging.version`). If *use_egg_info* is true, legacy egg - distributions will be considered as well. - - -Utility functions ------------------ - -.. function:: distinfo_dirname(name, version) - - Escape *name* and *version* into a filename-safe form and return the - directory name built from them, for example - :file:`{safename}-{safeversion}.dist-info.` In *name*, runs of - non-alphanumeric characters are replaced with one ``'_'``; in *version*, - spaces become dots, and runs of other non-alphanumeric characters (except - dots) a replaced by one ``'-'``. - - .. XXX wth spaces in version numbers? - -For performance purposes, the list of distributions is being internally -cached. Caching is enabled by default, but you can control it with these -functions: - -.. function:: clear_cache() - - Clear the cache. - -.. function:: disable_cache() - - Disable the cache, without clearing it. - -.. function:: enable_cache() - - Enable the internal cache, without clearing it. - - -Examples --------- - -Printing all information about a distribution -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Given the name of an installed distribution, we shall print out all -information that can be obtained using functions provided in this module:: - - import sys - import packaging.database - - try: - name = sys.argv[1] - except ValueError: - sys.exit('Not enough arguments') - - # first create the Distribution instance - dist = packaging.database.Distribution(path) - if dist is None: - sys.exit('No such distribution') - - print('Information about %r' % dist.name) - print() - - print('Files') - print('=====') - for path, md5, size in dist.list_installed_files(): - print('* Path: %s' % path) - print(' Hash %s, Size: %s bytes' % (md5, size)) - print() - - print('Metadata') - print('========') - for key, value in dist.metadata.items(): - print('%20s: %s' % (key, value)) - print() - - print('Extra') - print('=====') - if dist.requested: - print('* It was installed by user request') - else: - print('* It was installed as a dependency') - -If we save the script above as ``print_info.py``, we can use it to extract -information from a :file:`.dist-info` directory. By typing in the console: - -.. code-block:: sh - - python print_info.py choxie - -we get the following output: - -.. code-block:: none - - Information about 'choxie' - - Files - ===== - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py - Hash 5e052db6a478d06bad9ae033e6bc08af, Size: 111 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py - Hash ac56bf496d8d1d26f866235b95f31030, Size: 214 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py - Hash 416aab08dfa846f473129e89a7625bbc, Size: 25 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER - Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA - Hash 696a209967fef3c8b8f5a7bb10386385, Size: 225 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED - Hash d41d8cd98f00b204e9800998ecf8427e, Size: 0 bytes - * Path: ../tmp/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD - Hash None, Size: None bytes - - Metadata - ======== - Metadata-Version: 1.2 - Name: choxie - Version: 2.0.0.9 - Platform: [] - Supported-Platform: UNKNOWN - Summary: Chocolate with a kick! - Description: UNKNOWN - Keywords: [] - Home-page: UNKNOWN - Author: UNKNOWN - Author-email: UNKNOWN - Maintainer: UNKNOWN - Maintainer-email: UNKNOWN - License: UNKNOWN - Classifier: [] - Download-URL: UNKNOWN - Obsoletes-Dist: ['truffles (<=0.8,>=0.5)', 'truffles (<=0.9,>=0.6)'] - Project-URL: [] - Provides-Dist: ['truffles (1.0)'] - Requires-Dist: ['towel-stuff (0.1)'] - Requires-Python: UNKNOWN - Requires-External: [] - - Extra - ===== - * It was installed as a dependency - - -Getting metadata about a distribution -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Sometimes you're not interested about the packaging information contained in a -full :class:`Distribution` object but just want to do something with its -:attr:`~Distribution.metadata`:: - - >>> from packaging.database import get_distribution - >>> info = get_distribution('chocolate').metadata - >>> info['Keywords'] - ['cooking', 'happiness'] - - -Finding out obsoleted distributions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Now, we tackle a different problem, we are interested in finding out -which distributions have been obsoleted. This can be easily done as follows:: - - import packaging.database - - # iterate over all distributions in the system - for dist in packaging.database.get_distributions(): - name, version = dist.name, dist.version - # find out which distributions obsolete this name/version combination - replacements = packaging.database.obsoletes_distribution(name, version) - if replacements: - print('%r %s is obsoleted by' % (name, version), - ', '.join(repr(r.name) for r in replacements)) - -This is how the output might look like: - -.. code-block:: none - - 'strawberry' 0.6 is obsoleted by 'choxie' - 'grammar' 1.0a4 is obsoleted by 'towel-stuff' diff --git a/Doc/library/packaging.depgraph.rst b/Doc/library/packaging.depgraph.rst deleted file mode 100644 index c384788e9b6b..000000000000 --- a/Doc/library/packaging.depgraph.rst +++ /dev/null @@ -1,199 +0,0 @@ -:mod:`packaging.depgraph` --- Dependency graph builder -====================================================== - -.. module:: packaging.depgraph - :synopsis: Graph builder for dependencies between releases. - - -This module provides the means to analyse the dependencies between various -distributions and to create a graph representing these dependency relationships. -In this document, "distribution" refers to an instance of -:class:`packaging.database.Distribution` or -:class:`packaging.database.EggInfoDistribution`. - -.. XXX terminology problem with dist vs. release: dists are installed, but deps - use releases - -.. XXX explain how to use it with dists not installed: Distribution can only be - instantiated with a path, but this module is useful for remote dist too - -.. XXX functions should accept and return iterators, not lists - - -The :class:`DependencyGraph` class ----------------------------------- - -.. class:: DependencyGraph - - Represent a dependency graph between releases. The nodes are distribution - instances; the edge model dependencies. An edge from ``a`` to ``b`` means - that ``a`` depends on ``b``. - - .. method:: add_distribution(distribution) - - Add *distribution* to the graph. - - .. method:: add_edge(x, y, label=None) - - Add an edge from distribution *x* to distribution *y* with the given - *label* (string). - - .. method:: add_missing(distribution, requirement) - - Add a missing *requirement* (string) for the given *distribution*. - - .. method:: repr_node(dist, level=1) - - Print a subgraph starting from *dist*. *level* gives the depth of the - subgraph. - - Direct access to the graph nodes and edges is provided through these - attributes: - - .. attribute:: adjacency_list - - Dictionary mapping 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). - - .. attribute:: reverse_list - - Dictionary mapping distributions to a list of predecessors. This allows - efficient traversal. - - .. attribute:: missing - - Dictionary mapping distributions to a list of requirements that were not - provided by any distribution. - - -Auxiliary functions -------------------- - -.. function:: dependent_dists(dists, dist) - - Recursively generate a list of distributions from *dists* that are dependent - on *dist*. - - .. XXX what does member mean here: "dist is a member of *dists* for which we - are interested" - -.. function:: generate_graph(dists) - - Generate a :class:`DependencyGraph` from the given list of distributions. - - .. XXX make this alternate constructor a DepGraph classmethod or rename; - 'generate' can suggest it creates a file or an image, use 'make' - -.. function:: graph_to_dot(graph, f, skip_disconnected=True) - - Write a DOT output for the graph to the file-like object *f*. - - If *skip_disconnected* is true, all distributions that are not dependent on - any other distribution are skipped. - - .. XXX why is this not a DepGraph method? - - -Example Usage -------------- - -Depict all dependenciess in the system -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -First, we shall generate a graph of all the distributions on the system -and then create an image out of it using the tools provided by -`Graphviz `_:: - - from packaging.database import get_distributions - from packaging.depgraph import generate_graph - - dists = list(get_distributions()) - graph = generate_graph(dists) - -It would be interesting to print out the missing requirements. This can be done -as follows:: - - for dist, reqs in graph.missing.items(): - if reqs: - reqs = ' ,'.join(repr(req) for req in reqs) - print('Missing dependencies for %r: %s' % (dist.name, reqs)) - -Example output is: - -.. code-block:: none - - Missing dependencies for 'TurboCheetah': 'Cheetah' - Missing dependencies for 'TurboGears': 'ConfigObj', 'DecoratorTools', 'RuleDispatch' - Missing dependencies for 'jockey': 'PyKDE4.kdecore', 'PyKDE4.kdeui', 'PyQt4.QtCore', 'PyQt4.QtGui' - Missing dependencies for 'TurboKid': 'kid' - Missing dependencies for 'TurboJson: 'DecoratorTools', 'RuleDispatch' - -Now, we proceed with generating a graphical representation of the graph. First -we write it to a file, and then we generate a PNG image using the -:program:`dot` command-line tool:: - - from packaging.depgraph import graph_to_dot - with open('output.dot', 'w') as f: - # only show the interesting distributions, skipping the disconnected ones - graph_to_dot(graph, f, skip_disconnected=True) - -We can create the final picture using: - -.. code-block:: sh - - $ dot -Tpng output.dot > output.png - -An example result is: - -.. figure:: depgraph-output.png - :alt: Example PNG output from packaging.depgraph and dot - -If you want to include egg distributions as well, then the code requires only -one change, namely the line:: - - dists = list(packaging.database.get_distributions()) - -has to be replaced with:: - - dists = list(packaging.database.get_distributions(use_egg_info=True)) - -On many platforms, a richer graph is obtained because at the moment most -distributions are provided in the egg rather than the new standard -``.dist-info`` format. - -.. XXX missing image - - An example of a more involved graph for illustrative reasons can be seen - here: - - .. image:: depgraph_big.png - - -List all dependent distributions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -We will list all distributions that are dependent on some given distibution. -This time, egg distributions will be considered as well:: - - import sys - from packaging.database import get_distribution, get_distributions - from packaging.depgraph import dependent_dists - - dists = list(get_distributions(use_egg_info=True)) - dist = get_distribution('bacon', use_egg_info=True) - if dist is None: - sys.exit('No such distribution in the system') - - deps = dependent_dists(dists, dist) - deps = ', '.join(repr(x.name) for x in deps) - print('Distributions depending on %r: %s' % (dist.name, deps)) - -And this is example output: - -.. with the dependency relationships as in the previous section - (depgraph_big) - -.. code-block:: none - - Distributions depending on 'bacon': 'towel-stuff', 'choxie', 'grammar' diff --git a/Doc/library/packaging.dist.rst b/Doc/library/packaging.dist.rst deleted file mode 100644 index 25cb62bc4b1f..000000000000 --- a/Doc/library/packaging.dist.rst +++ /dev/null @@ -1,108 +0,0 @@ -:mod:`packaging.dist` --- The Distribution class -================================================ - -.. module:: packaging.dist - :synopsis: Core Distribution class. - - -This module provides the :class:`Distribution` class, which represents the -module distribution being built/packaged/distributed/installed. - -.. class:: Distribution(arguments) - - A :class:`Distribution` describes how to build, package, distribute and - install a Python project. - - The arguments accepted by the constructor are laid out in the following - table. Some of them will end up in a metadata object, the rest will become - data attributes of the :class:`Distribution` instance. - - .. TODO improve constructor to take a Metadata object + named params? - (i.e. Distribution(metadata, cmdclass, py_modules, etc) - .. TODO also remove obsolete(?) script_name, etc. parameters? see what - py2exe and other tools need - - +--------------------+--------------------------------+-------------------------------------------------------------+ - | argument name | value | type | - +====================+================================+=============================================================+ - | *name* | The name of the project | a string | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *version* | The version number of the | a string | - | | release; see | | - | | :mod:`packaging.version` | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *summary* | A single line describing the | a string | - | | project | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *description* | Longer description of the | a string | - | | project | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *author* | The name of the project author | a string | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *author_email* | The email address of the | a string | - | | project author | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *maintainer* | The name of the current | a string | - | | maintainer, if different from | | - | | the author | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *maintainer_email* | The email address of the | a string | - | | current maintainer, if | | - | | different from the author | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *home_page* | A URL for the proejct | a string | - | | (homepage) | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *download_url* | A URL to download the project | a string | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *packages* | A list of Python packages that | a list of strings | - | | packaging will manipulate | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *py_modules* | A list of Python modules that | a list of strings | - | | packaging will manipulate | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *scripts* | A list of standalone scripts | a list of strings | - | | to be built and installed | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *ext_modules* | A list of Python extensions to | a list of instances of | - | | be built | :class:`packaging.compiler.extension.Extension` | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *classifiers* | A list of categories for the | a list of strings; valid classifiers are listed on `PyPi | - | | distribution | `_. | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *distclass* | the :class:`Distribution` | a subclass of | - | | class to use | :class:`packaging.dist.Distribution` | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *script_name* | The name of the setup.py | a string | - | | script - defaults to | | - | | ``sys.argv[0]`` | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *script_args* | Arguments to supply to the | a list of strings | - | | setup script | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *options* | default options for the setup | a string | - | | script | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *license* | The license for the | a string | - | | distribution; should be used | | - | | when there is no suitable | | - | | License classifier, or to | | - | | refine a classifier | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *keywords* | Descriptive keywords; used by | a list of strings or a comma-separated string | - | | catalogs such as PyPI | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *platforms* | Platforms compatible with this | a list of strings or a comma-separated string | - | | distribution; should be used | | - | | when there is no suitable | | - | | Platform classifier | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *cmdclass* | A mapping of command names to | a dictionary | - | | :class:`Command` subclasses | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *data_files* | A list of data files to | a list | - | | install | | - +--------------------+--------------------------------+-------------------------------------------------------------+ - | *package_dir* | A mapping of Python packages | a dictionary | - | | to directory names | | - +--------------------+--------------------------------+-------------------------------------------------------------+ diff --git a/Doc/library/packaging.fancy_getopt.rst b/Doc/library/packaging.fancy_getopt.rst deleted file mode 100644 index 199cbcd21157..000000000000 --- a/Doc/library/packaging.fancy_getopt.rst +++ /dev/null @@ -1,75 +0,0 @@ -:mod:`packaging.fancy_getopt` --- Wrapper around the getopt module -================================================================== - -.. module:: packaging.fancy_getopt - :synopsis: Additional getopt functionality. - - -.. warning:: - This module is deprecated and will be replaced with :mod:`optparse`. - -This module provides a wrapper around the standard :mod:`getopt` module that -provides the following additional features: - -* short and long options are tied together - -* options have help strings, so :func:`fancy_getopt` could potentially create a - complete usage summary - -* options set attributes of a passed-in object - -* boolean options can have "negative aliases" --- e.g. if :option:`--quiet` is - the "negative alias" of :option:`--verbose`, then :option:`--quiet` on the - command line sets *verbose* to false. - -.. function:: fancy_getopt(options, negative_opt, object, args) - - Wrapper function. *options* is a list of ``(long_option, short_option, - help_string)`` 3-tuples as described in the constructor for - :class:`FancyGetopt`. *negative_opt* should be a dictionary mapping option names - to option names, both the key and value should be in the *options* list. - *object* is an object which will be used to store values (see the :meth:`getopt` - method of the :class:`FancyGetopt` class). *args* is the argument list. Will use - ``sys.argv[1:]`` if you pass ``None`` as *args*. - - -.. class:: FancyGetopt(option_table=None) - - The option_table is a list of 3-tuples: ``(long_option, short_option, - help_string)`` - - If an option takes an argument, its *long_option* should have ``'='`` appended; - *short_option* should just be a single character, no ``':'`` in any case. - *short_option* should be ``None`` if a *long_option* doesn't have a - corresponding *short_option*. All option tuples must have long options. - -The :class:`FancyGetopt` class provides the following methods: - - -.. method:: FancyGetopt.getopt(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 :class:`OptionDummy` instance, stores - option values there, and returns a tuple ``(args, object)``. If *object* is - supplied, it is modified in place and :func:`getopt` just returns *args*; in - both cases, the returned *args* is a modified copy of the passed-in *args* list, - which is left untouched. - - .. TODO and args returned are? - - -.. method:: FancyGetopt.get_option_order() - - Returns the list of ``(option, value)`` tuples processed by the previous run of - :meth:`getopt` Raises :exc:`RuntimeError` if :meth:`getopt` hasn't been called - yet. - - -.. method:: FancyGetopt.generate_help(header=None) - - Generate help text (a list of strings, one per suggested line of output) from - the option table for this :class:`FancyGetopt` object. - - If supplied, prints the supplied *header* at the top of the help. diff --git a/Doc/library/packaging.install.rst b/Doc/library/packaging.install.rst deleted file mode 100644 index 3e00750988fc..000000000000 --- a/Doc/library/packaging.install.rst +++ /dev/null @@ -1,112 +0,0 @@ -:mod:`packaging.install` --- Installation tools -=============================================== - -.. module:: packaging.install - :synopsis: Download and installation building blocks - - -Packaging provides a set of tools to deal with downloads and installation of -distributions. Their role is to download the distribution from indexes, resolve -the dependencies, and provide a safe way to install distributions. An operation -that fails will cleanly roll back, not leave half-installed distributions on the -system. Here's the basic process followed: - -#. Move all distributions that will be removed to a temporary location. - -#. Install all the distributions that will be installed in a temporary location. - -#. If the installation fails, move the saved distributions back to their - location and delete the installed distributions. - -#. Otherwise, move the installed distributions to the right location and delete - the temporary locations. - -This is a higher-level module built on :mod:`packaging.database` and -:mod:`packaging.pypi`. - - -Public functions ----------------- - -.. function:: get_infos(requirements, index=None, installed=None, \ - prefer_final=True) - - Return information about what's going to be installed and upgraded. - *requirements* is a string containing the requirements for this - project, for example ``'FooBar 1.1'`` or ``'BarBaz (<1.2)'``. - - .. XXX are requirements comma-separated? - - If you want to use another index than the main PyPI, give its URI as *index* - argument. - - *installed* is a list of already installed distributions used to find - satisfied dependencies, obsoleted distributions and eventual conflicts. - - By default, alpha, beta and candidate versions are not picked up. Set - *prefer_final* to false to accept them too. - - The results are returned in a dictionary containing all the information - needed to perform installation of the requirements with the - :func:`install_from_infos` function: - - >>> get_install_info("FooBar (<=1.2)") - {'install': [], 'remove': [], 'conflict': []} - - .. TODO should return tuple or named tuple, not dict - .. TODO use "predicate" or "requirement" consistently in version and here - .. FIXME "info" cannot be plural in English, s/infos/info/ - - -.. function:: install(project) - - -.. function:: install_dists(dists, path, paths=None) - - Safely install all distributions provided in *dists* into *path*. *paths* is - a list of paths where already-installed distributions will be looked for to - find satisfied dependencies and conflicts (default: :data:`sys.path`). - Returns a list of installed dists. - - .. FIXME dists are instances of what? - - -.. function:: install_from_infos(install_path=None, install=[], remove=[], \ - conflicts=[], paths=None) - - Safely install and remove given distributions. This function is designed to - work with the return value of :func:`get_infos`: *install*, *remove* and - *conflicts* should be list of distributions returned by :func:`get_infos`. - If *install* is not empty, *install_path* must be given to specify the path - where the distributions should be installed. *paths* is a list of paths - where already-installed distributions will be looked for (default: - :data:`sys.path`). - - This function is a very basic installer; if *conflicts* is not empty, the - system will be in a conflicting state after the function completes. It is a - building block for more sophisticated installers with conflict resolution - systems. - - .. TODO document typical value for install_path - .. TODO document integration with default schemes, esp. user site-packages - - -.. function:: install_local_project(path) - - Install a distribution from a source directory, which must contain either a - Packaging-compliant :file:`setup.cfg` file or a legacy Distutils - :file:`setup.py` script (in which case Distutils will be used under the hood - to perform the installation). - - -.. function:: remove(project_name, paths=None, auto_confirm=True) - - Remove one distribution from the system. - - .. FIXME this is the only function using "project" instead of dist/release - -.. - Example usage - -------------- - - Get the scheme of what's gonna be installed if we install "foobar": diff --git a/Doc/library/packaging.metadata.rst b/Doc/library/packaging.metadata.rst deleted file mode 100644 index 332d69df4f7b..000000000000 --- a/Doc/library/packaging.metadata.rst +++ /dev/null @@ -1,122 +0,0 @@ -:mod:`packaging.metadata` --- Metadata handling -=============================================== - -.. module:: packaging.metadata - :synopsis: Class holding the metadata of a release. - - -.. TODO use sphinx-autogen to generate basic doc from the docstrings - -.. class:: Metadata - - This class can read and write metadata files complying with any of the - defined versions: 1.0 (:PEP:`241`), 1.1 (:PEP:`314`) and 1.2 (:PEP:`345`). It - implements methods to parse Metadata files and write them, and a mapping - interface to its contents. - - The :PEP:`345` implementation supports the micro-language for the environment - markers, and displays warnings when versions that are supposed to be - :PEP:`386`-compliant are violating the specification. - - -Reading metadata ----------------- - -The :class:`Metadata` class can be instantiated -with the path of the metadata file, and provides a dict-like interface to the -values:: - - >>> from packaging.metadata import Metadata - >>> metadata = Metadata('PKG-INFO') - >>> metadata.keys()[:5] - ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform') - >>> metadata['Name'] - 'CLVault' - >>> metadata['Version'] - '0.5' - >>> metadata['Requires-Dist'] - ["pywin32; sys.platform == 'win32'", "Sphinx"] - - -The fields that support environment markers can be automatically ignored if -the object is instantiated using the ``platform_dependent`` option. -:class:`Metadata` will interpret in this case -the markers and will automatically remove the fields that are not compliant -with the running environment. Here's an example under Mac OS X. The win32 -dependency we saw earlier is ignored:: - - >>> from packaging.metadata import Metadata - >>> metadata = Metadata('PKG-INFO', platform_dependent=True) - >>> metadata['Requires-Dist'] - ['Sphinx'] - - -If you want to provide your own execution context, let's say to test the -metadata under a particular environment that is not the current environment, -you can provide your own values in the ``execution_context`` option, which -is the dict that may contain one or more keys of the context the micro-language -expects. - -Here's an example, simulating a win32 environment:: - - >>> from packaging.metadata import Metadata - >>> context = {'sys.platform': 'win32'} - >>> metadata = Metadata('PKG-INFO', platform_dependent=True, - ... execution_context=context) - ... - >>> metadata['Requires-Dist'] = ["pywin32; sys.platform == 'win32'", - ... "Sphinx"] - ... - >>> metadata['Requires-Dist'] - ['pywin32', 'Sphinx'] - - -Writing metadata ----------------- - -Writing metadata can be done using the ``write`` method:: - - >>> metadata.write('/to/my/PKG-INFO') - -The class will pick the best version for the metadata, depending on the values -provided. If all the values provided exist in all versions, the class will -use :attr:`PKG_INFO_PREFERRED_VERSION`. It is set by default to 1.0, the most -widespread version. - - -Conflict checking and best version ----------------------------------- - -Some fields in :PEP:`345` have to comply with the version number specification -defined in :PEP:`386`. When they don't comply, a warning is emitted:: - - >>> from packaging.metadata import Metadata - >>> metadata = Metadata() - >>> metadata['Requires-Dist'] = ['Funky (Groovie)'] - "Funky (Groovie)" is not a valid predicate - >>> metadata['Requires-Dist'] = ['Funky (1.2)'] - -See also :mod:`packaging.version`. - - -.. TODO talk about check() - - -:mod:`packaging.markers` --- Environment markers -================================================ - -.. module:: packaging.markers - :synopsis: Micro-language for environment markers - - -This is an implementation of environment markers `as defined in PEP 345 -`_. It is used -for some metadata fields. - -.. function:: interpret(marker, execution_context=None) - - Interpret a marker and return a boolean result depending on the environment. - Example: - - >>> interpret("python_version > '1.0'") - True diff --git a/Doc/library/packaging.pypi.dist.rst b/Doc/library/packaging.pypi.dist.rst deleted file mode 100644 index aaaaab7fb8db..000000000000 --- a/Doc/library/packaging.pypi.dist.rst +++ /dev/null @@ -1,114 +0,0 @@ -:mod:`packaging.pypi.dist` --- Classes representing query results -================================================================= - -.. module:: packaging.pypi.dist - :synopsis: Classes representing the results of queries to indexes. - - -Information coming from the indexes is held in instances of the classes defined -in this module. - -Keep in mind that each project (eg. FooBar) can have several releases -(eg. 1.1, 1.2, 1.3), and each of these releases can be provided in multiple -distributions (eg. a source distribution, a binary one, etc). - - -ReleaseInfo ------------ - -Each release has a project name, version, metadata, and related distributions. - -This information is stored in :class:`ReleaseInfo` -objects. - -.. class:: ReleaseInfo - - -DistInfo ---------- - -:class:`DistInfo` is a simple class that contains -information related to distributions; mainly the URLs where distributions -can be found. - -.. class:: DistInfo - - -ReleasesList ------------- - -The :mod:`~packaging.pypi.dist` module provides a class which works -with lists of :class:`ReleaseInfo` classes; -used to filter and order results. - -.. class:: ReleasesList - - -Example usage -------------- - -Build a list of releases and order them -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Assuming we have a list of releases:: - - >>> from packaging.pypi.dist import ReleasesList, ReleaseInfo - >>> fb10 = ReleaseInfo("FooBar", "1.0") - >>> fb11 = ReleaseInfo("FooBar", "1.1") - >>> fb11a = ReleaseInfo("FooBar", "1.1a1") - >>> ReleasesList("FooBar", [fb11, fb11a, fb10]) - >>> releases.sort_releases() - >>> releases.get_versions() - ['1.1', '1.1a1', '1.0'] - >>> releases.add_release("1.2a1") - >>> releases.get_versions() - ['1.1', '1.1a1', '1.0', '1.2a1'] - >>> releases.sort_releases() - ['1.2a1', '1.1', '1.1a1', '1.0'] - >>> releases.sort_releases(prefer_final=True) - >>> releases.get_versions() - ['1.1', '1.0', '1.2a1', '1.1a1'] - - -Add distribution related information to releases -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -It's easy to add distribution information to releases:: - - >>> from packaging.pypi.dist import ReleasesList, ReleaseInfo - >>> r = ReleaseInfo("FooBar", "1.0") - >>> r.add_distribution("sdist", url="http://example.org/foobar-1.0.tar.gz") - >>> r.dists - {'sdist': FooBar 1.0 sdist} - >>> r['sdist'].url - {'url': 'http://example.org/foobar-1.0.tar.gz', 'hashname': None, 'hashval': - None, 'is_external': True} - - -Getting attributes from the dist objects -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To abstract querying information returned from the indexes, attributes and -release information can be retrieved directly from dist objects. - -For instance, if you have a release instance that does not contain the metadata -attribute, it can be fetched by using the "fetch_metadata" method:: - - >>> r = Release("FooBar", "1.1") - >>> print r.metadata - None # metadata field is actually set to "None" - >>> r.fetch_metadata() - - -.. XXX add proper roles to these constructs - - -It's possible to retrieve a project's releases (`fetch_releases`), -metadata (`fetch_metadata`) and distributions (`fetch_distributions`) using -a similar work flow. - -.. XXX what is possible? - -Internally, this is possible because while retrieving information about -projects, releases or distributions, a reference to the client used is -stored which can be accessed using the objects `_index` attribute. diff --git a/Doc/library/packaging.pypi.rst b/Doc/library/packaging.pypi.rst deleted file mode 100644 index 14602cefc1c7..000000000000 --- a/Doc/library/packaging.pypi.rst +++ /dev/null @@ -1,74 +0,0 @@ -:mod:`packaging.pypi` --- Interface to projects indexes -======================================================= - -.. module:: packaging.pypi - :synopsis: Low-level and high-level APIs to query projects indexes. - - -Packaging queries PyPI to get information about projects or download them. The -low-level facilities used internally are also part of the public API designed to -be used by other tools. - -The :mod:`packaging.pypi` package provides those facilities, which can be -used to access information about Python projects registered at indexes, the -main one being PyPI, located ad http://pypi.python.org/. - -There is two ways to retrieve data from these indexes: a screen-scraping -interface called the "simple API", and XML-RPC. The first one uses HTML pages -located under http://pypi.python.org/simple/, the second one makes XML-RPC -requests to http://pypi.python.org/pypi/. All functions and classes also work -with other indexes such as mirrors, which typically implement only the simple -interface. - -Packaging provides a class that wraps both APIs to provide full query and -download functionality: :class:`packaging.pypi.client.ClientWrapper`. If you -want more control, you can use the underlying classes -:class:`packaging.pypi.simple.Crawler` and :class:`packaging.pypi.xmlrpc.Client` -to connect to one specific interface. - - -:mod:`packaging.pypi.client` --- High-level query API -===================================================== - -.. module:: packaging.pypi.client - :synopsis: Wrapper around :mod;`packaging.pypi.xmlrpc` and - :mod:`packaging.pypi.simple` to query indexes. - - -This module provides a high-level API to query indexes and search -for releases and distributions. The aim of this module is to choose the best -way to query the API automatically, either using XML-RPC or the simple index, -with a preference toward the latter. - -.. class:: ClientWrapper - - Instances of this class will use the simple interface or XML-RPC requests to - query indexes and return :class:`packaging.pypi.dist.ReleaseInfo` and - :class:`packaging.pypi.dist.ReleasesList` objects. - - .. method:: find_projects - - .. method:: get_release - - .. method:: get_releases - - -:mod:`packaging.pypi.base` --- Base class for index crawlers -============================================================ - -.. module:: packaging.pypi.base - :synopsis: Base class used to implement crawlers. - - -.. class:: BaseClient(prefer_final, prefer_source) - - Base class containing common methods for the index crawlers or clients. One - method is currently defined: - - .. method:: download_distribution(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 directory for the download. If a release is - found, the full path to the downloaded file is returned. diff --git a/Doc/library/packaging.pypi.simple.rst b/Doc/library/packaging.pypi.simple.rst deleted file mode 100644 index f579b18a4c51..000000000000 --- a/Doc/library/packaging.pypi.simple.rst +++ /dev/null @@ -1,218 +0,0 @@ -:mod:`packaging.pypi.simple` --- Crawler using the PyPI "simple" interface -========================================================================== - -.. module:: packaging.pypi.simple - :synopsis: Crawler using the screen-scraping "simple" interface to fetch info - and distributions. - - -The class provided by :mod:`packaging.pypi.simple` can access project indexes -and provide useful information about distributions. PyPI, other indexes and -local indexes are supported. - -You should use this module to search distributions by name and versions, process -index external pages and download distributions. It is not suited for things -that will end up in too long index processing (like "finding all distributions -with a specific version, no matter the name"); use :mod:`packaging.pypi.xmlrpc` -for that. - - -API ---- - -.. class:: Crawler(index_url=DEFAULT_SIMPLE_INDEX_URL, \ - prefer_final=False, prefer_source=True, \ - hosts=('*',), follow_externals=False, \ - mirrors_url=None, mirrors=None, timeout=15, \ - mirrors_max_tries=0) - - *index_url* is the address of the index to use for requests. - - The first two parameters control the query results. *prefer_final* - indicates whether a final version (not alpha, beta or candidate) is to be - preferred over a newer but non-final version (for example, whether to pick - up 1.0 over 2.0a3). It is used only for queries that don't give a version - argument. Likewise, *prefer_source* tells whether to prefer a source - distribution over a binary one, if no distribution argument was prodived. - - Other parameters are related to external links (that is links that go - outside the simple index): *hosts* is a list of hosts allowed to be - processed if *follow_externals* is true (default behavior is to follow all - hosts), *follow_externals* enables or disables following external links - (default is false, meaning disabled). - - The remaining parameters are related to the mirroring infrastructure - defined in :PEP:`381`. *mirrors_url* gives a URL to look on for DNS - records giving mirror adresses; *mirrors* is a list of mirror URLs (see - the PEP). If both *mirrors* and *mirrors_url* are given, *mirrors_url* - will only be used if *mirrors* is set to ``None``. *timeout* is the time - (in seconds) to wait before considering a URL has timed out; - *mirrors_max_tries"* is the number of times to try requesting informations - on mirrors before switching. - - The following methods are defined: - - .. method:: get_distributions(project_name, version) - - Return the distributions found in the index for the given release. - - .. method:: get_metadata(project_name, version) - - Return the metadata found on the index for this project name and - version. Currently downloads and unpacks a distribution to read the - PKG-INFO file. - - .. method:: get_release(requirements, prefer_final=None) - - Return one release that fulfills the given requirements. - - .. method:: get_releases(requirements, prefer_final=None, force_update=False) - - Search for releases and return a - :class:`~packaging.pypi.dist.ReleasesList` object containing the - results. - - .. method:: search_projects(name=None) - - Search the index for projects containing the given name and return a - list of matching names. - - See also the base class :class:`packaging.pypi.base.BaseClient` for inherited - methods. - - -.. data:: DEFAULT_SIMPLE_INDEX_URL - - The address used by default by the crawler class. It is currently - ``'http://a.pypi.python.org/simple/'``, the main PyPI installation. - - - - -Usage Examples ---------------- - -To help you understand how using the `Crawler` class, here are some basic -usages. - -Request the simple index to get a specific distribution -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Supposing you want to scan an index to get a list of distributions for -the "foobar" project. You can use the "get_releases" method for that. -The get_releases method will browse the project page, and return -:class:`ReleaseInfo` objects for each found link that rely on downloads. :: - - >>> from packaging.pypi.simple import Crawler - >>> crawler = Crawler() - >>> crawler.get_releases("FooBar") - [, ] - - -Note that you also can request the client about specific versions, using version -specifiers (described in `PEP 345 -`_):: - - >>> client.get_releases("FooBar < 1.2") - [, ] - - -`get_releases` returns a list of :class:`ReleaseInfo`, but you also can get the -best distribution that fullfil your requirements, using "get_release":: - - >>> client.get_release("FooBar < 1.2") - - - -Download distributions -^^^^^^^^^^^^^^^^^^^^^^ - -As it can get the urls of distributions provided by PyPI, the `Crawler` -client also can download the distributions and put it for you in a temporary -destination:: - - >>> client.download("foobar") - /tmp/temp_dir/foobar-1.2.tar.gz - - -You also can specify the directory you want to download to:: - - >>> client.download("foobar", "/path/to/my/dir") - /path/to/my/dir/foobar-1.2.tar.gz - - -While downloading, the md5 of the archive will be checked, if not matches, it -will try another time, then if fails again, raise `MD5HashDoesNotMatchError`. - -Internally, that's not the Crawler which download the distributions, but the -`DistributionInfo` class. Please refer to this documentation for more details. - - -Following PyPI external links -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The default behavior for packaging is to *not* follow the links provided -by HTML pages in the "simple index", to find distributions related -downloads. - -It's possible to tell the PyPIClient to follow external links by setting the -`follow_externals` attribute, on instantiation or after:: - - >>> client = Crawler(follow_externals=True) - -or :: - - >>> client = Crawler() - >>> client.follow_externals = True - - -Working with external indexes, and mirrors -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The default `Crawler` behavior is to rely on the Python Package index stored -on PyPI (http://pypi.python.org/simple). - -As you can need to work with a local index, or private indexes, you can specify -it using the index_url parameter:: - - >>> client = Crawler(index_url="file://filesystem/path/") - -or :: - - >>> client = Crawler(index_url="http://some.specific.url/") - - -You also can specify mirrors to fallback on in case the first index_url you -provided doesnt respond, or not correctly. The default behavior for -`Crawler` is to use the list provided by Python.org DNS records, as -described in the :PEP:`381` about mirroring infrastructure. - -If you don't want to rely on these, you could specify the list of mirrors you -want to try by specifying the `mirrors` attribute. It's a simple iterable:: - - >>> mirrors = ["http://first.mirror","http://second.mirror"] - >>> client = Crawler(mirrors=mirrors) - - -Searching in the simple index -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -It's possible to search for projects with specific names in the package index. -Assuming you want to find all projects containing the "distutils" keyword:: - - >>> c.search_projects("distutils") - [, , , , , , ] - - -You can also search the projects starting with a specific text, or ending with -that text, using a wildcard:: - - >>> c.search_projects("distutils*") - [, , ] - - >>> c.search_projects("*distutils") - [, , , , ] diff --git a/Doc/library/packaging.pypi.xmlrpc.rst b/Doc/library/packaging.pypi.xmlrpc.rst deleted file mode 100644 index 5242e4c530d7..000000000000 --- a/Doc/library/packaging.pypi.xmlrpc.rst +++ /dev/null @@ -1,143 +0,0 @@ -:mod:`packaging.pypi.xmlrpc` --- Crawler using the PyPI XML-RPC interface -========================================================================= - -.. module:: packaging.pypi.xmlrpc - :synopsis: Client using XML-RPC requests to fetch info and distributions. - - -Indexes can be queried using XML-RPC calls, and Packaging provides a simple -way to interface with XML-RPC. - -You should **use** XML-RPC when: - -* Searching the index for projects **on other fields than project - names**. For instance, you can search for projects based on the - author_email field. -* Searching all the versions that have existed for a project. -* you want to retrieve METADATAs information from releases or - distributions. - - -You should **avoid using** XML-RPC method calls when: - -* Retrieving the last version of a project -* Getting the projects with a specific name and version. -* The simple index can match your needs - - -When dealing with indexes, keep in mind that the index queries will always -return you :class:`packaging.pypi.dist.ReleaseInfo` and -:class:`packaging.pypi.dist.ReleasesList` objects. - -Some methods here share common APIs with the one you can find on -:class:`packaging.pypi.simple`, internally, :class:`packaging.pypi.client` -is inherited by :class:`Client` - - -API ---- - -.. class:: Client - - -Usage examples --------------- - -Use case described here are use case that are not common to the other clients. -If you want to see all the methods, please refer to API or to usage examples -described in :class:`packaging.pypi.client.Client` - - -Finding releases -^^^^^^^^^^^^^^^^ - -It's a common use case to search for "things" within the index. We can -basically search for projects by their name, which is the most used way for -users (eg. "give me the last version of the FooBar project"). - -This can be accomplished using the following syntax:: - - >>> client = xmlrpc.Client() - >>> client.get_release("Foobar (<= 1.3)) - - >>> client.get_releases("FooBar (<= 1.3)") - [FooBar 1.1, FooBar 1.1.1, FooBar 1.2, FooBar 1.2.1] - - -And we also can find for specific fields:: - - >>> client.search_projects(field=value) - - -You could specify the operator to use, default is "or":: - - >>> client.search_projects(field=value, operator="and") - - -The specific fields you can search are: - -* name -* version -* author -* author_email -* maintainer -* maintainer_email -* home_page -* license -* summary -* description -* keywords -* platform -* download_url - - -Getting metadata information -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -XML-RPC is a preferred way to retrieve metadata information from indexes. -It's really simple to do so:: - - >>> client = xmlrpc.Client() - >>> client.get_metadata("FooBar", "1.1") - - - -Assuming we already have a :class:`packaging.pypi.ReleaseInfo` object defined, -it's possible to pass it to the xmlrpc client to retrieve and complete its -metadata:: - - >>> foobar11 = ReleaseInfo("FooBar", "1.1") - >>> client = xmlrpc.Client() - >>> returned_release = client.get_metadata(release=foobar11) - >>> returned_release - - - -Get all the releases of a project -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To retrieve all the releases for a project, you can build them using -`get_releases`:: - - >>> client = xmlrpc.Client() - >>> client.get_releases("FooBar") - [, , ] - - -Get information about distributions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Indexes have information about projects, releases **and** distributions. -If you're not familiar with those, please refer to the documentation of -:mod:`packaging.pypi.dist`. - -It's possible to retrieve information about distributions, e.g "what are the -existing distributions for this release ? How to retrieve them ?":: - - >>> client = xmlrpc.Client() - >>> release = client.get_distributions("FooBar", "1.1") - >>> release.dists - {'sdist': , 'bdist': } - -As you see, this does not return a list of distributions, but a release, -because a release can be used like a list of distributions. diff --git a/Doc/library/packaging.rst b/Doc/library/packaging.rst deleted file mode 100644 index c6bff47015ec..000000000000 --- a/Doc/library/packaging.rst +++ /dev/null @@ -1,75 +0,0 @@ -:mod:`packaging` --- Packaging support -====================================== - -.. module:: packaging - :synopsis: Packaging system and building blocks for other packaging systems. -.. sectionauthor:: Fred L. Drake, Jr. , distutils and packaging - contributors - - -The :mod:`packaging` package provides support for building, packaging, -distributing and installing additional projects into a Python installation. -Projects may include Python modules, extension modules, packages and scripts. -:mod:`packaging` also provides building blocks for other packaging systems -that are not tied to the command system. - -This manual is the reference documentation for those standalone building -blocks and for extending Packaging. If you're looking for the user-centric -guides to install a project or package your own code, head to `See also`__. - - -Building blocks ---------------- - -.. toctree:: - :maxdepth: 2 - - packaging-misc - packaging.version - packaging.metadata - packaging.database - packaging.depgraph - packaging.pypi - packaging.pypi.dist - packaging.pypi.simple - packaging.pypi.xmlrpc - packaging.install - - -The command machinery ---------------------- - -.. toctree:: - :maxdepth: 2 - - packaging.dist - packaging.command - packaging.compiler - packaging.fancy_getopt - - -Other utilities ----------------- - -.. toctree:: - :maxdepth: 2 - - packaging.util - packaging.tests.pypi_server - -.. XXX missing: compat config create (dir_util) run pypi.{base,mirrors} - - -.. __: - -.. seealso:: - - :ref:`packaging-index` - The manual for developers of Python projects who want to package and - distribute them. This describes how to use :mod:`packaging` to make - projects easily found and added to an existing Python installation. - - :ref:`packaging-install-index` - A user-centered manual which includes information on adding projects - into an existing Python installation. You do not need to be a Python - programmer to read this manual. diff --git a/Doc/library/packaging.tests.pypi_server.rst b/Doc/library/packaging.tests.pypi_server.rst deleted file mode 100644 index f3b77203f4c7..000000000000 --- a/Doc/library/packaging.tests.pypi_server.rst +++ /dev/null @@ -1,105 +0,0 @@ -:mod:`packaging.tests.pypi_server` --- PyPI mock server -======================================================= - -.. module:: packaging.tests.pypi_server - :synopsis: Mock server used to test PyPI-related modules and commands. - - -When you are testing code that works with Packaging, you might find these tools -useful. - - -The mock server ---------------- - -.. class:: PyPIServer - - PyPIServer is a class that implements an HTTP server running in a separate - thread. All it does is record the requests for further inspection. The recorded - data is available under ``requests`` attribute. The default - HTTP response can be overridden with the ``default_response_status``, - ``default_response_headers`` and ``default_response_data`` attributes. - - By default, when accessing the server with urls beginning with `/simple/`, - the server also record your requests, but will look for files under - the `/tests/pypiserver/simple/` path. - - You can tell the sever to serve static files for other paths. This could be - accomplished by using the `static_uri_paths` parameter, as below:: - - server = PyPIServer(static_uri_paths=["first_path", "second_path"]) - - - You need to create the content that will be served under the - `/tests/pypiserver/default` path. If you want to serve content from another - place, you also can specify another filesystem path (which needs to be under - `tests/pypiserver/`. This will replace the default behavior of the server, and - it will not serve content from the `default` dir :: - - server = PyPIServer(static_filesystem_paths=["path/to/your/dir"]) - - - If you just need to add some paths to the existing ones, you can do as shown, - keeping in mind that the server will always try to load paths in reverse order - (e.g here, try "another/super/path" then the default one) :: - - server = PyPIServer(test_static_path="another/super/path") - server = PyPIServer("another/super/path") - # or - server.static_filesystem_paths.append("another/super/path") - - - As a result of what, in your tests, while you need to use the PyPIServer, in - order to isolates the test cases, the best practice is to place the common files - in the `default` folder, and to create a directory for each specific test case:: - - server = PyPIServer(static_filesystem_paths = ["default", "test_pypi_server"], - static_uri_paths=["simple", "external"]) - - -Base class and decorator for tests ----------------------------------- - -.. class:: PyPIServerTestCase - - ``PyPIServerTestCase`` is a test case class with setUp and tearDown methods that - take care of a single PyPIServer instance attached as a ``pypi`` attribute on - the test class. Use it as one of the base classes in your test case:: - - - class UploadTestCase(PyPIServerTestCase): - - def test_something(self): - cmd = self.prepare_command() - cmd.ensure_finalized() - cmd.repository = self.pypi.full_address - cmd.run() - - environ, request_data = self.pypi.requests[-1] - self.assertEqual(request_data, EXPECTED_REQUEST_DATA) - - -.. decorator:: use_pypi_server - - You also can use a decorator for your tests, if you do not need the same server - instance along all you test case. So, you can specify, for each test method, - some initialisation parameters for the server. - - For this, you need to add a `server` parameter to your method, like this:: - - class SampleTestCase(TestCase): - - @use_pypi_server() - def test_something(self, server): - ... - - - The decorator will instantiate the server for you, and run and stop it just - before and after your method call. You also can pass the server initializer, - just like this:: - - class SampleTestCase(TestCase): - - @use_pypi_server("test_case_name") - def test_something(self, server): - ... diff --git a/Doc/library/packaging.util.rst b/Doc/library/packaging.util.rst deleted file mode 100644 index e628c32c6a2c..000000000000 --- a/Doc/library/packaging.util.rst +++ /dev/null @@ -1,155 +0,0 @@ -:mod:`packaging.util` --- Miscellaneous utility functions -========================================================= - -.. module:: packaging.util - :synopsis: Miscellaneous utility functions. - - -This module contains various helpers for the other modules. - -.. XXX a number of functions are missing, but the module may be split first - (it's ginormous right now, some things could go to compat for example) - -.. function:: get_platform() - - Return a string that identifies the current platform. This is used mainly to - distinguish platform-specific build directories and platform-specific built - distributions. Typically includes the OS name and version and the - architecture (as supplied by 'os.uname()'), although the exact information - included depends on the OS; e.g. for IRIX the architecture isn't particularly - important (IRIX only runs on SGI hardware), but for Linux the kernel version - isn't particularly important. - - Examples of returned values: - - * ``linux-i586`` - * ``linux-alpha`` - * ``solaris-2.6-sun4u`` - * ``irix-5.3`` - * ``irix64-6.2`` - - For non-POSIX platforms, currently just returns ``sys.platform``. - - For Mac OS X systems the OS version reflects the minimal version on which - binaries will run (that is, the value of ``MACOSX_DEPLOYMENT_TARGET`` - during the build of Python), not the OS version of the current system. - - For universal binary builds on Mac OS X the architecture value reflects - the univeral binary status instead of the architecture of the current - processor. For 32-bit universal binaries the architecture is ``fat``, - for 64-bit universal binaries the architecture is ``fat64``, and - for 4-way universal binaries the architecture is ``universal``. Starting - from Python 2.7 and Python 3.2 the architecture ``fat3`` is used for - a 3-way universal build (ppc, i386, x86_64) and ``intel`` is used for - a univeral build with the i386 and x86_64 architectures - - Examples of returned values on Mac OS X: - - * ``macosx-10.3-ppc`` - - * ``macosx-10.3-fat`` - - * ``macosx-10.5-universal`` - - * ``macosx-10.6-intel`` - - .. XXX reinvention of platform module? - - -.. function:: convert_path(pathname) - - Return 'pathname' as a name that will work on the native filesystem, i.e. - split it on '/' and put it back together again using the current directory - separator. Needed because filenames in the setup script are always supplied - in Unix style, and have to be converted to the local convention before we - can actually use them in the filesystem. Raises :exc:`ValueError` on - non-Unix-ish systems if *pathname* either starts or ends with a slash. - - -.. function:: change_root(new_root, pathname) - - Return *pathname* with *new_root* prepended. If *pathname* is relative, this - is equivalent to ``os.path.join(new_root,pathname)`` Otherwise, it requires - making *pathname* relative and then joining the two, which is tricky on - DOS/Windows. - - -.. function:: check_environ() - - Ensure that 'os.environ' has all the environment variables we guarantee that - users can use in config files, command-line options, etc. Currently this - includes: - - * :envvar:`HOME` - user's home directory (Unix only) - * :envvar:`PLAT` - description of the current platform, including hardware - and OS (see :func:`get_platform`) - - -.. function:: find_executable(executable, path=None) - - Search the path for a given executable name. - - -.. function:: execute(func, args, msg=None, dry_run=False) - - Perform some action that affects the outside world (for instance, writing to - the filesystem). Such actions are special because they are disabled by the - *dry_run* flag. This method takes care of all that bureaucracy for you; - all you have to do is supply the function to call and an argument tuple for - it (to embody the "external action" being performed), and an optional message - to print. - - -.. function:: newer(source, target) - - Return true if *source* exists and is more recently modified than *target*, - or if *source* exists and *target* doesn't. Return false if both exist and - *target* is the same age or newer than *source*. Raise - :exc:`PackagingFileError` if *source* does not exist. - - -.. function:: strtobool(val) - - Convert a string representation of truth to true (1) or false (0). - - True values are ``y``, ``yes``, ``t``, ``true``, ``on`` and ``1``; false - values are ``n``, ``no``, ``f``, ``false``, ``off`` and ``0``. Raises - :exc:`ValueError` if *val* is anything else. - - -.. function:: byte_compile(py_files, optimize=0, force=0, prefix=None, \ - base_dir=None, dry_run=0, direct=None) - - Byte-compile a collection of Python source files to either :file:`.pyc` or - :file:`.pyo` files in a :file:`__pycache__` subdirectory (see :pep:`3147`), - or to the same directory when using the distutils2 backport on Python - versions older than 3.2. - - *py_files* is a list of files to compile; any files that don't end in - :file:`.py` are silently skipped. *optimize* must be one of the following: - - * ``0`` - don't optimize (generate :file:`.pyc`) - * ``1`` - normal optimization (like ``python -O``) - * ``2`` - extra optimization (like ``python -OO``) - - This function is independent from the running Python's :option:`-O` or - :option:`-B` options; it is fully controlled by the parameters passed in. - - If *force* is true, all files are recompiled regardless of timestamps. - - The source filename encoded in each :term:`bytecode` file defaults to the filenames - listed in *py_files*; you can modify these with *prefix* and *basedir*. - *prefix* is a string that will be stripped off of each source filename, and - *base_dir* is a directory name that will be prepended (after *prefix* is - stripped). You can supply either or both (or neither) of *prefix* and - *base_dir*, as you wish. - - If *dry_run* is true, doesn't actually do anything that would affect the - filesystem. - - Byte-compilation is either done directly in this interpreter process with the - standard :mod:`py_compile` module, or indirectly by writing a temporary - script and executing it. Normally, you should let :func:`byte_compile` - figure out to use direct compilation or not (see the source for details). - The *direct* flag is used by the script generated in indirect mode; unless - you know what you're doing, leave it set to ``None``. diff --git a/Doc/library/packaging.version.rst b/Doc/library/packaging.version.rst deleted file mode 100644 index f36cdaba10c0..000000000000 --- a/Doc/library/packaging.version.rst +++ /dev/null @@ -1,104 +0,0 @@ -:mod:`packaging.version` --- Version number classes -=================================================== - -.. module:: packaging.version - :synopsis: Classes that represent project version numbers. - - -This module contains classes and functions useful to deal with version numbers. -It's an implementation of version specifiers `as defined in PEP 345 -`_. - - -Version numbers ---------------- - -.. class:: NormalizedVersion(self, s, error_on_huge_major_num=True) - - A specific version of a distribution, as described in PEP 345. *s* is a - string object containing the version number (for example ``'1.2b1'``), - *error_on_huge_major_num* a boolean specifying whether to consider an - apparent use of a year or full date as the major version number an error. - - The rationale for the second argument is that there were projects using years - or full dates as version numbers, which could cause problems with some - packaging systems sorting. - - Instances of this class can be compared and sorted:: - - >>> NormalizedVersion('1.2b1') < NormalizedVersion('1.2') - True - - :class:`NormalizedVersion` is used internally by :class:`VersionPredicate` to - do its work. - - -.. class:: IrrationalVersionError - - Exception raised when an invalid string is given to - :class:`NormalizedVersion`. - - >>> NormalizedVersion("irrational_version_number") - ... - IrrationalVersionError: irrational_version_number - - -.. function:: suggest_normalized_version(s) - - Before standardization in PEP 386, various schemes were in use. Packaging - provides a function to try to convert any string to a valid, normalized - version:: - - >>> suggest_normalized_version('2.1-rc1') - 2.1c1 - - - If :func:`suggest_normalized_version` can't make sense of the given string, - it will return ``None``:: - - >>> print(suggest_normalized_version('not a version')) - None - - -Version predicates ------------------- - -.. class:: VersionPredicate(predicate) - - This class deals with the parsing of field values like - ``ProjectName (>=version)``. - - .. method:: match(version) - - Test if a version number matches the predicate: - - >>> version = VersionPredicate("ProjectName (<1.2, >1.0)") - >>> version.match("1.2.1") - False - >>> version.match("1.1.1") - True - - -Validation helpers ------------------- - -If you want to use :term:`LBYL`-style checks instead of instantiating the -classes and catching :class:`IrrationalVersionError` and :class:`ValueError`, -you can use these functions: - -.. function:: is_valid_version(predicate) - - Check whether the given string is a valid version number. Example of valid - strings: ``'1.2'``, ``'4.2.0.dev4'``, ``'2.5.4.post2'``. - - -.. function:: is_valid_versions(predicate) - - Check whether the given string is a valid value for specifying multiple - versions, such as in the Requires-Python field. Example: ``'2.7, >=3.2'``. - - -.. function:: is_valid_predicate(predicate) - - Check whether the given string is a valid version predicate. Examples: - ``'some.project == 4.5, <= 4.7'``, ``'speciallib (> 1.0, != 1.4.2, < 2.0)'``. diff --git a/Doc/library/python.rst b/Doc/library/python.rst index 07eadb4faa2e..b67fbfc2816b 100644 --- a/Doc/library/python.rst +++ b/Doc/library/python.rst @@ -25,5 +25,4 @@ overview: inspect.rst site.rst fpectl.rst - packaging.rst distutils.rst diff --git a/Doc/library/site.rst b/Doc/library/site.rst index b9878973c725..071706a4287f 100644 --- a/Doc/library/site.rst +++ b/Doc/library/site.rst @@ -134,9 +134,9 @@ empty, and the path manipulations are skipped; however the import of :func:`getuserbase` hasn't been called yet. Default value is :file:`~/.local` for UNIX and Mac OS X non-framework builds, :file:`~/Library/Python/{X.Y}` for Mac framework builds, and - :file:`{%APPDATA%}\\Python` for Windows. This value is used by Packaging to + :file:`{%APPDATA%}\\Python` for Windows. This value is used by Distutils to compute the installation directories for scripts, data files, Python modules, - etc. for the :ref:`user installation scheme `. + etc. for the :ref:`user installation scheme `. See also :envvar:`PYTHONUSERBASE`. diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index 035e8d65872b..5c1e9ad2f61f 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -35,8 +35,7 @@ directories that don't exist already) and places a ``pyvenv.cfg`` file in it with a ``home`` key pointing to the Python installation the command was run from. It also creates a ``bin`` (or ``Scripts`` on Windows) subdirectory containing a copy of the ``python`` binary (or -binaries, in the case of Windows) and the ``pysetup3`` script (to -facilitate easy installation of packages from PyPI into the new virtualenv). +binaries, in the case of Windows). It also creates an (initially empty) ``lib/pythonX.Y/site-packages`` subdirectory (on Windows, this is ``Lib\site-packages``). diff --git a/Doc/packaging/builtdist.rst b/Doc/packaging/builtdist.rst deleted file mode 100644 index 1d9a3498a744..000000000000 --- a/Doc/packaging/builtdist.rst +++ /dev/null @@ -1,302 +0,0 @@ -.. _packaging-built-dist: - -**************************** -Creating Built Distributions -**************************** - -A "built distribution" is what you're probably used to thinking of either as a -"binary package" or an "installer" (depending on your background). It's not -necessarily binary, though, because it might contain only Python source code -and/or byte-code; and we don't call it a package, because that word is already -spoken for in Python. (And "installer" is a term specific to the world of -mainstream desktop systems.) - -A built distribution is how you make life as easy as possible for installers of -your module distribution: for users of RPM-based Linux systems, it's a binary -RPM; for Windows users, it's an executable installer; for Debian-based Linux -users, it's a Debian package; and so forth. Obviously, no one person will be -able to create built distributions for every platform under the sun, so the -Distutils are designed to enable module developers to concentrate on their -specialty---writing code and creating source distributions---while an -intermediary species called *packagers* springs up to turn source distributions -into built distributions for as many platforms as there are packagers. - -Of course, the module developer could be his own packager; or the packager could -be a volunteer "out there" somewhere who has access to a platform which the -original developer does not; or it could be software periodically grabbing new -source distributions and turning them into built distributions for as many -platforms as the software has access to. Regardless of who they are, a packager -uses the setup script and the :command:`bdist` command family to generate built -distributions. - -As a simple example, if I run the following command in the Distutils source -tree:: - - python setup.py bdist - -then the Distutils builds my module distribution (the Distutils itself in this -case), does a "fake" installation (also in the :file:`build` directory), and -creates the default type of built distribution for my platform. The default -format for built distributions is a "dumb" tar file on Unix, and a simple -executable installer on Windows. (That tar file is considered "dumb" because it -has to be unpacked in a specific location to work.) - -Thus, the above command on a Unix system creates -:file:`Distutils-1.0.{plat}.tar.gz`; unpacking this tarball from the right place -installs the Distutils just as though you had downloaded the source distribution -and run ``python setup.py install``. (The "right place" is either the root of -the filesystem or Python's :file:`{prefix}` directory, depending on the options -given to the :command:`bdist_dumb` command; the default is to make dumb -distributions relative to :file:`{prefix}`.) - -Obviously, for pure Python distributions, this isn't any simpler than just -running ``python setup.py install``\ ---but for non-pure distributions, which -include extensions that would need to be compiled, it can mean the difference -between someone being able to use your extensions or not. And creating "smart" -built distributions, such as an executable installer for -Windows, is far more convenient for users even if your distribution doesn't -include any extensions. - -The :command:`bdist` command has a :option:`--formats` option, similar to the -:command:`sdist` command, which you can use to select the types of built -distribution to generate: for example, :: - - python setup.py bdist --format=zip - -would, when run on a Unix system, create :file:`Distutils-1.0.{plat}.zip`\ ----again, this archive would be unpacked from the root directory to install the -Distutils. - -The available formats for built distributions are: - -+-------------+------------------------------+---------+ -| Format | Description | Notes | -+=============+==============================+=========+ -| ``gztar`` | gzipped tar file | (1),(3) | -| | (:file:`.tar.gz`) | | -+-------------+------------------------------+---------+ -| ``tar`` | tar file (:file:`.tar`) | \(3) | -+-------------+------------------------------+---------+ -| ``zip`` | zip file (:file:`.zip`) | (2),(4) | -+-------------+------------------------------+---------+ -| ``wininst`` | self-extracting ZIP file for | \(4) | -| | Windows | | -+-------------+------------------------------+---------+ -| ``msi`` | Microsoft Installer. | | -+-------------+------------------------------+---------+ - - -Notes: - -(1) - default on Unix - -(2) - default on Windows - -(3) - requires external utilities: :program:`tar` and possibly one of :program:`gzip` - or :program:`bzip2` - -(4) - requires either external :program:`zip` utility or :mod:`zipfile` module (part - of the standard Python library since Python 1.6) - -You don't have to use the :command:`bdist` command with the :option:`--formats` -option; you can also use the command that directly implements the format you're -interested in. Some of these :command:`bdist` "sub-commands" actually generate -several similar formats; for instance, the :command:`bdist_dumb` command -generates all the "dumb" archive formats (``tar``, ``gztar``, and -``zip``). The :command:`bdist` sub-commands, and the formats generated by -each, are: - -+--------------------------+-----------------------+ -| Command | Formats | -+==========================+=======================+ -| :command:`bdist_dumb` | tar, gztar, zip | -+--------------------------+-----------------------+ -| :command:`bdist_wininst` | wininst | -+--------------------------+-----------------------+ -| :command:`bdist_msi` | msi | -+--------------------------+-----------------------+ - -The following sections give details on the individual :command:`bdist_\*` -commands. - - -.. _packaging-creating-dumb: - -Creating dumb built distributions -================================= - -.. XXX Need to document absolute vs. prefix-relative packages here, but first - I have to implement it! - - -.. _packaging-creating-wininst: - -Creating Windows Installers -=========================== - -Executable installers are the natural format for binary distributions on -Windows. They display a nice graphical user interface, display some information -about the module distribution to be installed taken from the metadata in the -setup script, let the user select a few options, and start or cancel the -installation. - -Since the metadata is taken from the setup script, creating Windows installers -is usually as easy as running:: - - python setup.py bdist_wininst - -or the :command:`bdist` command with the :option:`--formats` option:: - - python setup.py bdist --formats=wininst - -If you have a pure module distribution (only containing pure Python modules and -packages), the resulting installer will be version independent and have a name -like :file:`foo-1.0.win32.exe`. These installers can even be created on Unix -platforms or Mac OS X. - -If you have a non-pure distribution, the extensions can only be created on a -Windows platform, and will be Python version dependent. The installer filename -will reflect this and now has the form :file:`foo-1.0.win32-py2.0.exe`. You -have to create a separate installer for every Python version you want to -support. - -The installer will try to compile pure modules into :term:`bytecode` after installation -on the target system in normal and optimizing mode. If you don't want this to -happen for some reason, you can run the :command:`bdist_wininst` command with -the :option:`--no-target-compile` and/or the :option:`--no-target-optimize` -option. - -By default the installer will display the cool "Python Powered" logo when it is -run, but you can also supply your own 152x261 bitmap which must be a Windows -:file:`.bmp` file with the :option:`--bitmap` option. - -The installer will also display a large title on the desktop background window -when it is run, which is constructed from the name of your distribution and the -version number. This can be changed to another text by using the -:option:`--title` option. - -The installer file will be written to the "distribution directory" --- normally -:file:`dist/`, but customizable with the :option:`--dist-dir` option. - -.. _packaging-cross-compile-windows: - -Cross-compiling on Windows -========================== - -Starting with Python 2.6, packaging is capable of cross-compiling between -Windows platforms. In practice, this means that with the correct tools -installed, you can use a 32bit version of Windows to create 64bit extensions -and vice-versa. - -To build for an alternate platform, specify the :option:`--plat-name` option -to the build command. Valid values are currently 'win32', 'win-amd64' and -'win-ia64'. For example, on a 32bit version of Windows, you could execute:: - - python setup.py build --plat-name=win-amd64 - -to build a 64bit version of your extension. The Windows Installers also -support this option, so the command:: - - python setup.py build --plat-name=win-amd64 bdist_wininst - -would create a 64bit installation executable on your 32bit version of Windows. - -To cross-compile, you must download the Python source code and cross-compile -Python itself for the platform you are targetting - it is not possible from a -binary installtion of Python (as the .lib etc file for other platforms are -not included.) In practice, this means the user of a 32 bit operating -system will need to use Visual Studio 2008 to open the -:file:`PCBuild/PCbuild.sln` solution in the Python source tree and build the -"x64" configuration of the 'pythoncore' project before cross-compiling -extensions is possible. - -Note that by default, Visual Studio 2008 does not install 64bit compilers or -tools. You may need to reexecute the Visual Studio setup process and select -these tools (using Control Panel->[Add/Remove] Programs is a convenient way to -check or modify your existing install.) - -.. _packaging-postinstallation-script: - -The Postinstallation script ---------------------------- - -Starting with Python 2.3, a postinstallation script can be specified with the -:option:`--install-script` option. The basename of the script must be -specified, and the script filename must also be listed in the scripts argument -to the setup function. - -This script will be run at installation time on the target system after all the -files have been copied, with ``argv[1]`` set to :option:`-install`, and again at -uninstallation time before the files are removed with ``argv[1]`` set to -:option:`-remove`. - -The installation script runs embedded in the windows installer, every output -(``sys.stdout``, ``sys.stderr``) is redirected into a buffer and will be -displayed in the GUI after the script has finished. - -Some functions especially useful in this context are available as additional -built-in functions in the installation script. - -.. currentmodule:: bdist_wininst-postinst-script - -.. function:: directory_created(path) - file_created(path) - - These functions should be called when a directory or file is created by the - postinstall script at installation time. It will register *path* with the - uninstaller, so that it will be removed when the distribution is uninstalled. - To be safe, directories are only removed if they are empty. - - -.. function:: get_special_folder_path(csidl_string) - - This function can be used to retrieve special folder locations on Windows like - the Start Menu or the Desktop. It returns the full path to the folder. - *csidl_string* must be one of the following strings:: - - "CSIDL_APPDATA" - - "CSIDL_COMMON_STARTMENU" - "CSIDL_STARTMENU" - - "CSIDL_COMMON_DESKTOPDIRECTORY" - "CSIDL_DESKTOPDIRECTORY" - - "CSIDL_COMMON_STARTUP" - "CSIDL_STARTUP" - - "CSIDL_COMMON_PROGRAMS" - "CSIDL_PROGRAMS" - - "CSIDL_FONTS" - - If the folder cannot be retrieved, :exc:`OSError` is raised. - - Which folders are available depends on the exact Windows version, and probably - also the configuration. For details refer to Microsoft's documentation of the - :c:func:`SHGetSpecialFolderPath` function. - - -.. function:: create_shortcut(target, description, filename[, arguments[, workdir[, iconpath[, iconindex]]]]) - - This function creates a shortcut. *target* is the path to the program to be - started by the shortcut. *description* is the description of the shortcut. - *filename* is the title of the shortcut that the user will see. *arguments* - specifies the command-line arguments, if any. *workdir* is the working directory - for the program. *iconpath* is the file containing the icon for the shortcut, - and *iconindex* is the index of the icon in the file *iconpath*. Again, for - details consult the Microsoft documentation for the :class:`IShellLink` - interface. - - -Vista User Access Control (UAC) -=============================== - -Starting with Python 2.6, bdist_wininst supports a :option:`--user-access-control` -option. The default is 'none' (meaning no UAC handling is done), and other -valid values are 'auto' (meaning prompt for UAC elevation if Python was -installed for all users) and 'force' (meaning always prompt for elevation). diff --git a/Doc/packaging/commandhooks.rst b/Doc/packaging/commandhooks.rst deleted file mode 100644 index b261d002703d..000000000000 --- a/Doc/packaging/commandhooks.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. TODO integrate this in commandref and configfile - -.. _packaging-command-hooks: - -============= -Command hooks -============= - -Packaging provides a way of extending its commands by the use of pre- and -post-command hooks. Hooks are Python functions (or any callable object) that -take a command object as argument. They're specified in :ref:`config files -` using their fully qualified names. After a -command is finalized (its options are processed), the pre-command hooks are -executed, then the command itself is run, and finally the post-command hooks are -executed. - -See also global setup hooks in :ref:`setupcfg-spec`. - - -.. _packaging-finding-hooks: - -Finding hooks -============= - -As a hook is configured with a Python dotted name, it must either be defined in -a module installed on the system, or in a module present in the project -directory, where the :file:`setup.cfg` file lives:: - - # file: _setuphooks.py - - def hook(install_cmd): - metadata = install_cmd.dist.metadata - print('Hooked while installing %r %s!' % (metadata['Name'], - metadata['Version'])) - -Then you need to configure it in :file:`setup.cfg`:: - - [install_dist] - pre-hook.a = _setuphooks.hook - -Packaging will add the project directory to :data:`sys.path` and find the -``_setuphooks`` module. - -Hooks defined in different config files (system-wide, user-wide and -project-wide) do not override each other as long as they are specified with -different aliases (additional names after the dot). The alias in the example -above is ``a``. diff --git a/Doc/packaging/commandref.rst b/Doc/packaging/commandref.rst deleted file mode 100644 index 2165b56fd400..000000000000 --- a/Doc/packaging/commandref.rst +++ /dev/null @@ -1,374 +0,0 @@ -.. _packaging-command-reference: - -***************** -Command Reference -***************** - -This reference briefly documents all standard Packaging commands and some of -their options. - -.. FIXME does not work: Use pysetup run --help-commands to list all - standard and extra commands availavble on your system, with their - description. Use pysetup run --help to get help about the options - of one command. - -.. XXX sections from this document should be merged with other docs (e.g. check - and upload with uploading.rst, install_* with install/install.rst, etc.); - there is no value in partially duplicating information. this file could - however serve as an index, i.e. just a list of all commands with links to - every section that describes options or usage - - -Preparing distributions -======================= - -:command:`check` ----------------- - -Perform some tests on the metadata of a distribution. - -For example, it verifies that all required metadata fields are provided in the -:file:`setup.cfg` file. - -.. TODO document reST checks - - -:command:`test` ---------------- - -Run a test suite. - -When doing test-driven development, or running automated builds that need -testing before they are installed for downloading or use, it's often useful to -be able to run a project's unit tests without actually installing the project -anywhere. The :command:`test` command runs project's unit tests without -actually installing it, by temporarily putting the project's source on -:data:`sys.path`, after first running :command:`build_ext -i` to ensure that any -C extensions are built. - -You can use this command in one of two ways: either by specifying a -unittest-compatible test suite for your project (or any callable that returns -it) or by passing a test runner function that will run your tests and display -results in the console. Both options take a Python dotted name in the form -``package.module.callable`` to specify the object to use. - -If none of these options are specified, Packaging will try to perform test -discovery using either unittest (for Python 3.2 and higher) or unittest2 (for -older versions, if installed). - -.. this is a pseudo-command name used to disambiguate the options in indexes and - links -.. program:: packaging test - -.. cmdoption:: --suite=NAME, -s NAME - - Specify the test suite (or module, class, or method) to be run. The default - for this option can be set by in the project's :file:`setup.cfg` file: - - .. code-block:: cfg - - [test] - suite = mypackage.tests.get_all_tests - -.. cmdoption:: --runner=NAME, -r NAME - - Specify the test runner to be called. - - -:command:`config` ------------------ - -Perform distribution configuration. - - -The build step -============== - -This step is mainly useful to compile C/C++ libraries or extension modules. The -build commands can be run manually to check for syntax errors or packaging -issues (for example if the addition of a new source file was forgotten in the -:file:`setup.cfg` file), and is also run automatically by commands which need -it. Packaging checks the mtime of source and built files to avoid re-building -if it's not necessary. - - -:command:`build` ----------------- - -Build all files of a distribution, delegating to the other :command:`build_*` -commands to do the work. - - -:command:`build_clib` ---------------------- - -Build C libraries. - - -:command:`build_ext` --------------------- - -Build C/C++ extension modules. - - -:command:`build_py` -------------------- - -Build the Python modules (just copy them to the build directory) and -:term:`byte-compile ` them to :file:`.pyc` and/or :file:`.pyo` files. - -The byte compilation is controlled by two sets of options: - -- ``--compile`` and ``--no-compile`` are used to control the creation of - :file:`.pyc` files; the default is ``--no-compile``. - -- ``--optimize N`` (or ``-ON``) is used to control the creation of :file:`.pyo` - files: ``-O1`` turns on basic optimizations, ``-O2`` also discards docstrings, - ``-O0`` does not create :file:`.pyo` files; the default is ``-O0``. - -You can mix and match these options: for example, ``--no-compile --optimize 2`` -will create :file:`.pyo` files but no :file:`.pyc` files. - -.. XXX these option roles do not work - -Calling Python with :option:`-O` or :option:`-B` does not control the creation -of bytecode files, only the options described above do. - - -:command:`build_scripts` ------------------------- -Build the scripts (just copy them to the build directory and adjust their -shebang if they're Python scripts). - - -:command:`clean` ----------------- - -Clean the build tree of the release. - -.. program:: packaging clean - -.. cmdoption:: --all, -a - - Remove build directories for modules, scripts, etc., not only temporary build - by-products. - - -Creating source and built distributions -======================================= - -:command:`sdist` ----------------- - -Build a source distribution for a release. - -It is recommended that you always build and upload a source distribution. Users -of OSes with easy access to compilers and users of advanced packaging tools will -prefer to compile from source rather than using pre-built distributions. For -Windows users, providing a binary installer is also recommended practice. - - -:command:`bdist` ----------------- - -Build a binary distribution for a release. - -This command will call other :command:`bdist_*` commands to create one or more -distributions depending on the options given. The default is to create a -.tar.gz archive on Unix and a zip archive on Windows or OS/2. - -.. program:: packaging bdist - -.. cmdoption:: --formats - - Binary formats to build (comma-separated list). - -.. cmdoption:: --show-formats - - Dump list of available formats. - - -:command:`bdist_dumb` ---------------------- - -Build a "dumb" installer, a simple archive of files that could be unpacked under -``$prefix`` or ``$exec_prefix``. - - -:command:`bdist_wininst` ------------------------- - -Build a Windows installer. - - -:command:`bdist_msi` --------------------- - -Build a `Microsoft Installer`_ (.msi) file. - -.. _Microsoft Installer: http://msdn.microsoft.com/en-us/library/cc185688(VS.85).aspx - -In most cases, the :command:`bdist_msi` installer is a better choice than the -:command:`bdist_wininst` installer, because it provides better support for Win64 -platforms, allows administrators to perform non-interactive installations, and -allows installation through group policies. - - -Publishing distributions -======================== - -:command:`register` -------------------- - -This command registers the current release with the Python Package Index. This -is described in more detail in :PEP:`301`. - -.. TODO explain user and project registration with the web UI - - -:command:`upload` ------------------ - -Upload source and/or binary distributions to PyPI. - -The distributions have to be built on the same command line as the -:command:`upload` command; see :ref:`packaging-package-upload` for more info. - -.. program:: packaging upload - -.. cmdoption:: --sign, -s - - Sign each uploaded file using GPG (GNU Privacy Guard). The ``gpg`` program - must be available for execution on the system ``PATH``. - -.. cmdoption:: --identity=NAME, -i NAME - - Specify the identity or key name for GPG to use when signing. The value of - this option will be passed through the ``--local-user`` option of the - ``gpg`` program. - -.. cmdoption:: --show-response - - Display the full response text from server; this is useful for debugging - PyPI problems. - -.. cmdoption:: --repository=URL, -r URL - - The URL of the repository to upload to. Defaults to - http://pypi.python.org/pypi (i.e., the main PyPI installation). - -.. cmdoption:: --upload-docs - - Also run :command:`upload_docs`. Mainly useful as a default value in - :file:`setup.cfg` (on the command line, it's shorter to just type both - commands). - - -:command:`upload_docs` ----------------------- - -Upload HTML documentation to PyPI. - -PyPI now supports publishing project documentation at a URI of the form -``http://packages.python.org/``. :command:`upload_docs` will create -the necessary zip file out of a documentation directory and will post to the -repository. - -Note that to upload the documentation of a project, the corresponding version -must already be registered with PyPI, using the :command:`register` command --- -just like with :command:`upload`. - -Assuming there is an ``Example`` project with documentation in the subdirectory -:file:`docs`, for example:: - - Example/ - example.py - setup.cfg - docs/ - build/ - html/ - index.html - tips_tricks.html - conf.py - index.txt - tips_tricks.txt - -You can simply specify the directory with the HTML files in your -:file:`setup.cfg` file: - -.. code-block:: cfg - - [upload_docs] - upload-dir = docs/build/html - - -.. program:: packaging upload_docs - -.. cmdoption:: --upload-dir - - The directory to be uploaded to the repository. By default documentation - is searched for in ``docs`` (or ``doc``) directory in project root. - -.. cmdoption:: --show-response - - Display the full response text from server; this is useful for debugging - PyPI problems. - -.. cmdoption:: --repository=URL, -r URL - - The URL of the repository to upload to. Defaults to - http://pypi.python.org/pypi (i.e., the main PyPI installation). - - -The install step -================ - -These commands are used by end-users of a project using :program:`pysetup` or -another compatible installer. Each command will run the corresponding -:command:`build_*` command and then move the built files to their destination on -the target system. - - -:command:`install_dist` ------------------------ - -Install a distribution, delegating to the other :command:`install_*` commands to -do the work. See :ref:`packaging-how-install-works` for complete usage -instructions. - - -:command:`install_data` ------------------------ - -Install data files. - - -:command:`install_distinfo` ---------------------------- - -Install files recording details of the installation as specified in :PEP:`376`. - - -:command:`install_headers` --------------------------- - -Install C/C++ header files. - - -:command:`install_lib` ----------------------- - -Install all modules (extensions and pure Python). - -.. XXX what about C libraries created with build_clib? - -Similarly to ``build_py``, there are options to control the compilation of -Python code to :term:`bytecode` files (see above). By default, :file:`.pyc` -files will be created (``--compile``) and :file:`.pyo` files will not -(``--optimize 0``). - - -:command:`install_scripts` --------------------------- - -Install scripts. diff --git a/Doc/packaging/configfile.rst b/Doc/packaging/configfile.rst deleted file mode 100644 index 825b5cb55896..000000000000 --- a/Doc/packaging/configfile.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. _packaging-setup-config: - -************************************ -Writing the Setup Configuration File -************************************ - -Often, it's not possible to write down everything needed to build a distribution -*a priori*: you may need to get some information from the user, or from the -user's system, in order to proceed. As long as that information is fairly -simple---a list of directories to search for C header files or libraries, for -example---then providing a configuration file, :file:`setup.cfg`, for users to -edit is a cheap and easy way to solicit it. Configuration files also let you -provide default values for any command option, which the installer can then -override either on the command line or by editing the config file. - -The setup configuration file is a useful middle-ground between the setup script ----which, ideally, would be opaque to installers [#]_---and the command line to -the setup script, which is outside of your control and entirely up to the -installer. In fact, :file:`setup.cfg` (and any other Distutils configuration -files present on the target system) are processed after the contents of the -setup script, but before the command line. This has several useful -consequences: - -.. If you have more advanced needs, such as determining which extensions to - build based on what capabilities are present on the target system, then you - need the Distutils auto-configuration facility. This started to appear in - Distutils 0.9 but, as of this writing, isn't mature or stable enough yet - for real-world use. - -* installers can override some of what you put in :file:`setup.py` by editing - :file:`setup.cfg` - -* you can provide non-standard defaults for options that are not easily set in - :file:`setup.py` - -* installers can override anything in :file:`setup.cfg` using the command-line - options to :file:`setup.py` - -The basic syntax of the configuration file is simple:: - - [command] - option = value - ... - -where *command* is one of the Distutils commands (e.g. :command:`build_py`, -:command:`install_dist`), and *option* is one of the options that command supports. -Any number of options can be supplied for each command, and any number of -command sections can be included in the file. Blank lines are ignored, as are -comments, which run from a ``'#'`` character until the end of the line. Long -option values can be split across multiple lines simply by indenting the -continuation lines. - -You can find out the list of options supported by a particular command with the -universal :option:`--help` option, e.g. :: - - > python setup.py --help build_ext - [...] - Options for 'build_ext' command: - --build-lib (-b) directory for compiled extension modules - --build-temp (-t) directory for temporary files (build by-products) - --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 - --define (-D) C preprocessor macros to define - --undef (-U) C preprocessor macros to undefine - --swig-opts list of SWIG command-line options - [...] - -.. XXX do we want to support ``setup.py --help metadata``? - -Note that an option spelled :option:`--foo-bar` on the command line is spelled -:option:`foo_bar` in configuration files. - -For example, say you want your extensions to be built "in-place"---that is, you -have an extension :mod:`pkg.ext`, and you want the compiled extension file -(:file:`ext.so` on Unix, say) to be put in the same source directory as your -pure Python modules :mod:`pkg.mod1` and :mod:`pkg.mod2`. You can always use the -:option:`--inplace` option on the command line to ensure this:: - - python setup.py build_ext --inplace - -But this requires that you always specify the :command:`build_ext` command -explicitly, and remember to provide :option:`--inplace`. An easier way is to -"set and forget" this option, by encoding it in :file:`setup.cfg`, the -configuration file for this distribution:: - - [build_ext] - inplace = 1 - -This will affect all builds of this module distribution, whether or not you -explicitly specify :command:`build_ext`. If you include :file:`setup.cfg` in -your source distribution, it will also affect end-user builds---which is -probably a bad idea for this option, since always building extensions in-place -would break installation of the module distribution. In certain peculiar cases, -though, modules are built right in their installation directory, so this is -conceivably a useful ability. (Distributing extensions that expect to be built -in their installation directory is almost always a bad idea, though.) - -Another example: certain commands take options that vary from project to -project but not depending on the installation system, for example, -:command:`test` needs to know where your test suite is located and what test -runner to use; likewise, :command:`upload_docs` can find HTML documentation in -a :file:`doc` or :file:`docs` directory, but needs an option to find files in -:file:`docs/build/html`. Instead of having to type out these options each -time you want to run the command, you can put them in the project's -:file:`setup.cfg`:: - - [test] - suite = packaging.tests - - [upload_docs] - upload-dir = docs/build/html - - -.. seealso:: - - :ref:`packaging-config-syntax` in "Installing Python Projects" - More information on the configuration files is available in the manual for - system administrators. - - -.. rubric:: Footnotes - -.. [#] This ideal probably won't be achieved until auto-configuration is fully - supported by the Distutils. diff --git a/Doc/packaging/examples.rst b/Doc/packaging/examples.rst deleted file mode 100644 index 594ade01d74c..000000000000 --- a/Doc/packaging/examples.rst +++ /dev/null @@ -1,334 +0,0 @@ -.. _packaging-examples: - -******** -Examples -******** - -This chapter provides a number of basic examples to help get started with -Packaging. - - -.. _packaging-pure-mod: - -Pure Python distribution (by module) -==================================== - -If you're just distributing a couple of modules, especially if they don't live -in a particular package, you can specify them individually using the -:option:`py_modules` option in the setup script. - -In the simplest case, you'll have two files to worry about: a setup script and -the single module you're distributing, :file:`foo.py` in this example:: - - / - setup.py - foo.py - -(In all diagrams in this section, ** will refer to the distribution root -directory.) A minimal setup script to describe this situation would be:: - - from packaging.core import setup - setup(name='foo', - version='1.0', - py_modules=['foo']) - -Note that the name of the distribution is specified independently with the -:option:`name` option, and there's no rule that says it has to be the same as -the name of the sole module in the distribution (although that's probably a good -convention to follow). However, the distribution name is used to generate -filenames, so you should stick to letters, digits, underscores, and hyphens. - -Since :option:`py_modules` is a list, you can of course specify multiple -modules, e.g. if you're distributing modules :mod:`foo` and :mod:`bar`, your -setup might look like this:: - - / - setup.py - foo.py - bar.py - -and the setup script might be :: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - py_modules=['foo', 'bar']) - -You can put module source files into another directory, but if you have enough -modules to do that, it's probably easier to specify modules by package rather -than listing them individually. - - -.. _packaging-pure-pkg: - -Pure Python distribution (by package) -===================================== - -If you have more than a couple of modules to distribute, especially if they are -in multiple packages, it's probably easier to specify whole packages rather than -individual modules. This works even if your modules are not in a package; you -can just tell the Distutils to process modules from the root package, and that -works the same as any other package (except that you don't have to have an -:file:`__init__.py` file). - -The setup script from the last example could also be written as :: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - packages=['']) - -(The empty string stands for the root package.) - -If those two files are moved into a subdirectory, but remain in the root -package, e.g.:: - - / - setup.py - src/ - foo.py - bar.py - -then you would still specify the root package, but you have to tell the -Distutils where source files in the root package live:: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - package_dir={'': 'src'}, - packages=['']) - -More typically, though, you will want to distribute multiple modules in the same -package (or in sub-packages). For example, if the :mod:`foo` and :mod:`bar` -modules belong in package :mod:`foobar`, one way to lay out your source tree is - -:: - - / - setup.py - foobar/ - __init__.py - foo.py - bar.py - -This is in fact the default layout expected by the Distutils, and the one that -requires the least work to describe in your setup script:: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - packages=['foobar']) - -If you want to put modules in directories not named for their package, then you -need to use the :option:`package_dir` option again. For example, if the -:file:`src` directory holds modules in the :mod:`foobar` package:: - - / - setup.py - src/ - __init__.py - foo.py - bar.py - -an appropriate setup script would be :: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - package_dir={'foobar': 'src'}, - packages=['foobar']) - -Or, you might put modules from your main package right in the distribution -root:: - - / - setup.py - __init__.py - foo.py - bar.py - -in which case your setup script would be :: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - package_dir={'foobar': ''}, - packages=['foobar']) - -(The empty string also stands for the current directory.) - -If you have sub-packages, they must be explicitly listed in :option:`packages`, -but any entries in :option:`package_dir` automatically extend to sub-packages. -(In other words, the Distutils does *not* scan your source tree, trying to -figure out which directories correspond to Python packages by looking for -:file:`__init__.py` files.) Thus, if the default layout grows a sub-package:: - - / - setup.py - foobar/ - __init__.py - foo.py - bar.py - subfoo/ - __init__.py - blah.py - -then the corresponding setup script would be :: - - from packaging.core import setup - setup(name='foobar', - version='1.0', - packages=['foobar', 'foobar.subfoo']) - -(Again, the empty string in :option:`package_dir` stands for the current -directory.) - - -.. _packaging-single-ext: - -Single extension module -======================= - -Extension modules are specified using the :option:`ext_modules` option. -:option:`package_dir` has no effect on where extension source files are found; -it only affects the source for pure Python modules. The simplest case, a -single extension module in a single C source file, is:: - - / - setup.py - foo.c - -If the :mod:`foo` extension belongs in the root package, the setup script for -this could be :: - - from packaging.core import setup, Extension - setup(name='foobar', - version='1.0', - ext_modules=[Extension('foo', ['foo.c'])]) - -If the extension actually belongs in a package, say :mod:`foopkg`, then - -With exactly the same source tree layout, this extension can be put in the -:mod:`foopkg` package simply by changing the name of the extension:: - - from packaging.core import setup, Extension - setup(name='foobar', - version='1.0', - packages=['foopkg'], - ext_modules=[Extension('foopkg.foo', ['foo.c'])]) - - -Checking metadata -================= - -The ``check`` command allows you to verify if your project's metadata -meets the minimum requirements to build a distribution. - -To run it, just call it using your :file:`setup.py` script. If something is -missing, ``check`` will display a warning. - -Let's take an example with a simple script:: - - from packaging.core import setup - - setup(name='foobar') - -.. TODO configure logging StreamHandler to match this output - -Running the ``check`` command will display some warnings:: - - $ python setup.py check - running check - warning: check: missing required metadata: version, home_page - warning: check: missing metadata: either (author and author_email) or - (maintainer and maintainer_email) must be supplied - - -If you use the reStructuredText syntax in the ``long_description`` field and -`Docutils `_ is installed you can check if -the syntax is fine with the ``check`` command, using the ``restructuredtext`` -option. - -For example, if the :file:`setup.py` script is changed like this:: - - from packaging.core import setup - - desc = """\ - Welcome to foobar! - =============== - - This is the description of the ``foobar`` project. - """ - - setup(name='foobar', - version='1.0', - author=u'Tarek Ziadé', - author_email='tarek@ziade.org', - summary='Foobar utilities' - description=desc, - home_page='http://example.com') - -Where the long description is broken, ``check`` will be able to detect it -by using the :mod:`docutils` parser:: - - $ python setup.py check --restructuredtext - running check - warning: check: Title underline too short. (line 2) - warning: check: Could not finish the parsing. - - -.. _packaging-reading-metadata: - -Reading the metadata -==================== - -The :func:`packaging.core.setup` function provides a command-line interface -that allows you to query the metadata fields of a project through the -:file:`setup.py` script of a given project:: - - $ python setup.py --name - foobar - -This call reads the ``name`` metadata by running the -:func:`packaging.core.setup` function. When a source or binary -distribution is created with Distutils, the metadata fields are written -in a static file called :file:`PKG-INFO`. When a Distutils-based project is -installed in Python, the :file:`PKG-INFO` file is copied alongside the modules -and packages of the distribution under :file:`NAME-VERSION-pyX.X.egg-info`, -where ``NAME`` is the name of the project, ``VERSION`` its version as defined -in the Metadata, and ``pyX.X`` the major and minor version of Python like -``2.7`` or ``3.2``. - -You can read back this static file, by using the -:class:`packaging.dist.Metadata` class and its -:func:`read_pkg_file` method:: - - >>> from packaging.metadata import Metadata - >>> metadata = Metadata() - >>> metadata.read_pkg_file(open('distribute-0.6.8-py2.7.egg-info')) - >>> metadata.name - 'distribute' - >>> metadata.version - '0.6.8' - >>> metadata.description - 'Easily download, build, install, upgrade, and uninstall Python packages' - -Notice that the class can also be instantiated with a metadata file path to -loads its values:: - - >>> pkg_info_path = 'distribute-0.6.8-py2.7.egg-info' - >>> Metadata(pkg_info_path).name - 'distribute' - - -.. XXX These comments have been here for at least ten years. Write the - sections or delete the comments (we can maybe ask Greg Ward about - the planned contents). (Unindent to make them section titles) - - .. multiple-ext:: - - Multiple extension modules - ========================== - - Putting it all together - ======================= diff --git a/Doc/packaging/extending.rst b/Doc/packaging/extending.rst deleted file mode 100644 index f2d386317160..000000000000 --- a/Doc/packaging/extending.rst +++ /dev/null @@ -1,95 +0,0 @@ -.. _extending-packaging: - -******************* -Extending Distutils -******************* - -Distutils can be extended in various ways. Most extensions take the form of new -commands or replacements for existing commands. New commands may be written to -support new types of platform-specific packaging, for example, while -replacements for existing commands may be made to modify details of how the -command operates on a package. - -Most extensions of the packaging are made within :file:`setup.py` scripts that -want to modify existing commands; many simply add a few file extensions that -should be copied into packages in addition to :file:`.py` files as a -convenience. - -Most packaging command implementations are subclasses of the -:class:`packaging.cmd.Command` class. New commands may directly inherit from -:class:`Command`, while replacements often derive from :class:`Command` -indirectly, directly subclassing the command they are replacing. Commands are -required to derive from :class:`Command`. - -.. .. _extend-existing: - Extending existing commands - =========================== - - -.. .. _new-commands: - Writing new commands - ==================== - - -Integrating new commands -======================== - -There are different ways to integrate new command implementations into -packaging. The most difficult is to lobby for the inclusion of the new features -in packaging itself, and wait for (and require) a version of Python that -provides that support. This is really hard for many reasons. - -The most common, and possibly the most reasonable for most needs, is to include -the new implementations with your :file:`setup.py` script, and cause the -:func:`packaging.core.setup` function use them:: - - from packaging.core import setup - from packaging.command.build_py import build_py as _build_py - - class build_py(_build_py): - """Specialized Python source builder.""" - - # implement whatever needs to be different... - - setup(..., cmdclass={'build_py': build_py}) - -This approach is most valuable if the new implementations must be used to use a -particular package, as everyone interested in the package will need to have the -new command implementation. - -Beginning with Python 2.4, a third option is available, intended to allow new -commands to be added which can support existing :file:`setup.py` scripts without -requiring modifications to the Python installation. This is expected to allow -third-party extensions to provide support for additional packaging systems, but -the commands can be used for anything packaging commands can be used for. A new -configuration option, :option:`command_packages` (command-line option -:option:`--command-packages`), can be used to specify additional packages to be -searched for modules implementing commands. Like all packaging options, this -can be specified on the command line or in a configuration file. This option -can only be set in the ``[global]`` section of a configuration file, or before -any commands on the command line. If set in a configuration file, it can be -overridden from the command line; setting it to an empty string on the command -line causes the default to be used. This should never be set in a configuration -file provided with a package. - -This new option can be used to add any number of packages to the list of -packages searched for command implementations; multiple package names should be -separated by commas. When not specified, the search is only performed in the -:mod:`packaging.command` package. When :file:`setup.py` is run with the option -:option:`--command-packages` :option:`distcmds,buildcmds`, however, the packages -:mod:`packaging.command`, :mod:`distcmds`, and :mod:`buildcmds` will be searched -in that order. New commands are expected to be implemented in modules of the -same name as the command by classes sharing the same name. Given the example -command-line option above, the command :command:`bdist_openpkg` could be -implemented by the class :class:`distcmds.bdist_openpkg.bdist_openpkg` or -:class:`buildcmds.bdist_openpkg.bdist_openpkg`. - - -Adding new distribution types -============================= - -Commands that create distributions (files in the :file:`dist/` directory) need -to add ``(command, filename)`` pairs to ``self.distribution.dist_files`` so that -:command:`upload` can upload it to PyPI. The *filename* in the pair contains no -path information, only the name of the file itself. In dry-run mode, pairs -should still be added to represent what would have been created. diff --git a/Doc/packaging/index.rst b/Doc/packaging/index.rst deleted file mode 100644 index d3d0dec4de35..000000000000 --- a/Doc/packaging/index.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. _packaging-index: - -############################## - Distributing Python Projects -############################## - -:Authors: The Fellowship of the Packaging -:Email: distutils-sig@python.org -:Release: |version| -:Date: |today| - -This document describes Packaging for Python authors, describing how to use the -module to make Python applications, packages or modules easily available to a -wider audience with very little overhead for build/release/install mechanics. - -.. toctree:: - :maxdepth: 2 - :numbered: - - tutorial - setupcfg - introduction - setupscript - configfile - sourcedist - builtdist - packageindex - uploading - examples - extending - commandhooks - commandref - - -.. seealso:: - - :ref:`packaging-install-index` - A user-centered manual which includes information on adding projects - into an existing Python installation. You do not need to be a Python - programmer to read this manual. - - :mod:`packaging` - A library reference for developers of packaging tools wanting to use - standalone building blocks like :mod:`~packaging.version` or - :mod:`~packaging.metadata`, or extend Packaging itself. diff --git a/Doc/packaging/introduction.rst b/Doc/packaging/introduction.rst deleted file mode 100644 index a757ffc38bd2..000000000000 --- a/Doc/packaging/introduction.rst +++ /dev/null @@ -1,193 +0,0 @@ -.. _packaging-intro: - -***************************** -An Introduction to Packaging -***************************** - -This document covers using Packaging to distribute your Python modules, -concentrating on the role of developer/distributor. If you're looking for -information on installing Python modules you should refer to the -:ref:`packaging-install-index` chapter. - -Throughout this documentation, the terms "Distutils", "the Distutils" and -"Packaging" will be used interchangeably. - -.. _packaging-concepts: - -Concepts & Terminology -====================== - -Using Distutils is quite simple both for module developers and for -users/administrators installing third-party modules. As a developer, your -responsibilities (apart from writing solid, well-documented and well-tested -code, of course!) are: - -* writing a setup script (:file:`setup.py` by convention) - -* (optional) writing a setup configuration file - -* creating a source distribution - -* (optional) creating one or more "built" (binary) distributions of your - project - -All of these tasks are covered in this document. - -Not all module developers have access to multiple platforms, so one cannot -expect them to create buildt distributions for every platform. To remedy -this, it is hoped that intermediaries called *packagers* will arise to address -this need. Packagers take source distributions released by module developers, -build them on one or more platforms and release the resulting built -distributions. Thus, users on a greater range of platforms will be able to -install the most popular Python modules in the most natural way for their -platform without having to run a setup script or compile a single line of code. - - -.. _packaging-simple-example: - -A Simple Example -================ - -A setup script is usually quite simple, although since it's written in Python -there are no arbitrary limits to what you can do with it, though you should be -careful about putting expensive operations in your setup script. -Unlike, say, Autoconf-style configure scripts the setup script may be run -multiple times in the course of building and installing a module -distribution. - -If all you want to do is distribute a module called :mod:`foo`, contained in a -file :file:`foo.py`, then your setup script can be as simple as:: - - from packaging.core import setup - setup(name='foo', - version='1.0', - py_modules=['foo']) - -Some observations: - -* most information that you supply to the Distutils is supplied as keyword - arguments to the :func:`setup` function - -* those keyword arguments fall into two categories: package metadata (name, - version number, etc.) and information about what's in the package (a list - of pure Python modules in this case) - -* modules are specified by module name, not filename (the same will hold true - for packages and extensions) - -* it's recommended that you supply a little more metadata than we have in the - example. In particular your name, email address and a URL for the - project if appropriate (see section :ref:`packaging-setup-script` for an example) - -To create a source distribution for this module you would create a setup -script, :file:`setup.py`, containing the above code and run:: - - python setup.py sdist - -which will create an archive file (e.g., tarball on Unix, ZIP file on Windows) -containing your setup script :file:`setup.py`, and your module :file:`foo.py`. -The archive file will be named :file:`foo-1.0.tar.gz` (or :file:`.zip`), and -will unpack into a directory :file:`foo-1.0`. - -If an end-user wishes to install your :mod:`foo` module all he has to do is -download :file:`foo-1.0.tar.gz` (or :file:`.zip`), unpack it, and from the -:file:`foo-1.0` directory run :: - - python setup.py install - -which will copy :file:`foo.py` to the appropriate directory for -third-party modules in their Python installation. - -This simple example demonstrates some fundamental concepts of Distutils. -First, both developers and installers have the same basic user interface, i.e. -the setup script. The difference is which Distutils *commands* they use: the -:command:`sdist` command is almost exclusively for module developers, while -:command:`install` is more often used by installers (although some developers -will want to install their own code occasionally). - -If you want to make things really easy for your users, you can create more -than one built distributions for them. For instance, if you are running on a -Windows machine and want to make things easy for other Windows users, you can -create an executable installer (the most appropriate type of built distribution -for this platform) with the :command:`bdist_wininst` command. For example:: - - python setup.py bdist_wininst - -will create an executable installer, :file:`foo-1.0.win32.exe`, in the current -directory. You can find out what distribution formats are available at any time -by running :: - - python setup.py bdist --help-formats - - -.. _packaging-python-terms: - -General Python terminology -========================== - -If you're reading this document, you probably have a good idea of what Python -modules, extensions and so forth are. Nevertheless, just to be sure that -everyone is on the same page, here's a quick overview of Python terms: - -module - The basic unit of code reusability in Python: a block of code imported by - some other code. Three types of modules are important to us here: pure - Python modules, extension modules and packages. - -pure Python module - A module written in Python and contained in a single :file:`.py` file (and - possibly associated :file:`.pyc` and/or :file:`.pyo` files). Sometimes - referred to as a "pure module." - -extension module - A module written in the low-level language of the Python implementation: C/C++ - for Python, Java for Jython. Typically contained in a single dynamically - loaded pre-compiled file, e.g. a shared object (:file:`.so`) file for Python - extensions on Unix, a DLL (given the :file:`.pyd` extension) for Python - extensions on Windows, or a Java class file for Jython extensions. Note that - currently Distutils only handles C/C++ extensions for Python. - -package - A module that contains other modules, typically contained in a directory of - the filesystem and distinguished from other directories by the presence of a - file :file:`__init__.py`. - -root package - The root of the hierarchy of packages. (This isn't really a package, - since it doesn't have an :file:`__init__.py` file. But... we have to - call it something, right?) The vast majority of the standard library is - in the root package, as are many small standalone third-party modules that - don't belong to a larger module collection. Unlike regular packages, - modules in the root package can be found in many directories: in fact, - every directory listed in ``sys.path`` contributes modules to the root - package. - - -.. _packaging-term: - -Distutils-specific terminology -============================== - -The following terms apply more specifically to the domain of distributing Python -modules using Distutils: - -module distribution - A collection of Python modules distributed together as a single downloadable - resource and meant to be installed all as one. Examples of some well-known - module distributions are NumPy, SciPy, PIL (the Python Imaging - Library) or mxBase. (Module distributions would be called a *package*, - except that term is already taken in the Python context: a single module - distribution may contain zero, one, or many Python packages.) - -pure module distribution - A module distribution that contains only pure Python modules and packages. - Sometimes referred to as a "pure distribution." - -non-pure module distribution - A module distribution that contains at least one extension module. Sometimes - referred to as a "non-pure distribution." - -distribution root - The top-level directory of your source tree (or source distribution). The - directory where :file:`setup.py` exists. Generally :file:`setup.py` will - be run from this directory. diff --git a/Doc/packaging/packageindex.rst b/Doc/packaging/packageindex.rst deleted file mode 100644 index cd1d5986312b..000000000000 --- a/Doc/packaging/packageindex.rst +++ /dev/null @@ -1,104 +0,0 @@ -.. _packaging-package-index: - -********************************** -Registering with the Package Index -********************************** - -The Python Package Index (PyPI) holds metadata describing distributions -packaged with packaging. The packaging command :command:`register` is used to -submit your distribution's metadata to the index. It is invoked as follows:: - - python setup.py register - -Distutils will respond with the following prompt:: - - running register - 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]: - -Note: if your username and password are saved locally, you will not see this -menu. - -If you have not registered with PyPI, then you will need to do so now. You -should choose option 2, and enter your details as required. Soon after -submitting your details, you will receive an email which will be used to confirm -your registration. - -Once you are registered, you may choose option 1 from the menu. You will be -prompted for your PyPI username and password, and :command:`register` will then -submit your metadata to the index. - -You may submit any number of versions of your distribution to the index. If you -alter the metadata for a particular version, you may submit it again and the -index will be updated. - -PyPI holds a record for each (name, version) combination submitted. The first -user to submit information for a given name is designated the Owner of that -name. They may submit changes through the :command:`register` command or through -the web interface. They may also designate other users as Owners or Maintainers. -Maintainers may edit the package information, but not designate other Owners or -Maintainers. - -By default PyPI will list all versions of a given package. To hide certain -versions, the Hidden property should be set to yes. This must be edited through -the web interface. - - -.. _packaging-pypirc: - -The .pypirc file -================ - -The format of the :file:`.pypirc` file is as follows:: - - [packaging] - index-servers = - pypi - - [pypi] - repository: - username: - password: - -The *packaging* section defines a *index-servers* variable that lists the -name of all sections describing a repository. - -Each section describing a repository defines three variables: - -- *repository*, that defines the url of the PyPI server. Defaults to - ``http://www.python.org/pypi``. -- *username*, which is the registered username on the PyPI server. -- *password*, that will be used to authenticate. If omitted the user - will be prompt to type it when needed. - -If you want to define another server a new section can be created and -listed in the *index-servers* variable:: - - [packaging] - index-servers = - pypi - other - - [pypi] - repository: - username: - password: - - [other] - repository: http://example.com/pypi - username: - password: - -:command:`register` can then be called with the -r option to point the -repository to work with:: - - python setup.py register -r http://example.com/pypi - -For convenience, the name of the section that describes the repository -may also be used:: - - python setup.py register -r other diff --git a/Doc/packaging/setupcfg.rst b/Doc/packaging/setupcfg.rst deleted file mode 100644 index a38101751ed6..000000000000 --- a/Doc/packaging/setupcfg.rst +++ /dev/null @@ -1,890 +0,0 @@ -.. highlightlang:: cfg - -.. _setupcfg-spec: - -******************************************* -Specification of the :file:`setup.cfg` file -******************************************* - -:version: 0.9 - -This document describes the :file:`setup.cfg`, an ini-style configuration file -used by Packaging to replace the :file:`setup.py` file used by Distutils. -This specification is language-agnostic, and will therefore repeat some -information that's already documented for Python in the -:class:`configparser.RawConfigParser` documentation. - -.. contents:: - :depth: 3 - :local: - - -.. _setupcfg-syntax: - -Syntax -====== - -The ini-style format used in the configuration file is a simple collection of -sections that group sets of key-value fields separated by ``=`` or ``:`` and -optional whitespace. Lines starting with ``#`` or ``;`` are comments and will -be ignored. Empty lines are also ignored. Example:: - - [section1] - # comment - name = value - name2 = "other value" - - [section2] - foo = bar - - -Parsing values ---------------- - -Here are a set of rules to parse values: - -- If a value is quoted with ``"`` chars, it's a string. If a quote character is - present in the quoted value, it can be escaped as ``\"`` or left as-is. - -- If the value is ``true``, ``t``, ``yes``, ``y`` (case-insensitive) or ``1``, - it's converted to the language equivalent of a ``True`` value; if it's - ``false``, ``f``, ``no``, ``n`` (case-insensitive) or ``0``, it's converted to - the equivalent of ``False``. - -- A value can contain multiple lines. When read, lines are converted into a - sequence of values. Each line after the first must start with a least one - space or tab character; this leading indentation will be stripped. - -- All other values are considered strings. - -Examples:: - - [section] - foo = one - two - three - - bar = false - baz = 1.3 - boo = "ok" - beee = "wqdqw pojpj w\"ddq" - - -Extending files ---------------- - -A configuration file can be extended (i.e. included) by other files. For this, -a ``DEFAULT`` section must contain an ``extends`` key whose value points to one -or more files which will be merged into the current files by adding new sections -and fields. If a file loaded by ``extends`` contains sections or keys that -already exist in the original file, they will not override the previous values. - -Contents of :file:`one.cfg`:: - - [section1] - name = value - - [section2] - foo = foo from one.cfg - -Contents of :file:`two.cfg`:: - - [DEFAULT] - extends = one.cfg - - [section2] - foo = foo from two.cfg - baz = baz from two.cfg - -The result of parsing :file:`two.cfg` is equivalent to this file:: - - [section1] - name = value - - [section2] - foo = foo from one.cfg - baz = baz from two.cfg - -Example use of multi-line notation to include more than one file:: - - [DEFAULT] - extends = one.cfg - two.cfg - -When several files are provided, they are processed sequentially, following the -precedence rules explained above. This means that the list of files should go -from most specialized to most common. - -**Tools will need to provide a way to produce a merged version of the -file**. This will be useful to let users publish a single file. - - -.. _setupcfg-sections: - -Description of sections and fields -================================== - -Each section contains a description of its options. - -- Options that are marked *multi* can have multiple values, one value per - line. -- Options that are marked *optional* can be omitted. -- Options that are marked *environ* can use environment markers, as described - in :PEP:`345`. - - -The sections are: - -global - Global options not related to one command. - -metadata - Name, version and other information defined by :PEP:`345`. - -files - Modules, scripts, data, documentation and other files to include in the - distribution. - -extension sections - Options used to build extension modules. - -command sections - Options given for specific commands, identical to those that can be given - on the command line. - - -.. _setupcfg-section-global: - -Global options --------------- - -Contains global options for Packaging. This section is shared with Distutils. - - -commands - Defined Packaging command. A command is defined by its fully - qualified name. *optional*, *multi* - - Examples:: - - [global] - commands = - package.setup.CustomSdistCommand - package.setup.BdistDeb - -compilers - Defined Packaging compiler. A compiler is defined by its fully - qualified name. *optional*, *multi* - - Example:: - - [global] - compilers = - hotcompiler.SmartCCompiler - -setup_hooks - Defines a list of callables to be called right after the :file:`setup.cfg` - file is read, before any other processing. Each value is a Python dotted - name to an object, which has to be defined in a module present in the project - directory alonside :file:`setup.cfg` or on Python's :data:`sys.path` (see - :ref:`packaging-finding-hooks`). The callables are executed in the - order they're found in the file; if one of them cannot be found, tools should - not stop, but for example produce a warning and continue with the next line. - Each callable receives the configuration as a dictionary (keys are - :file:`setup.cfg` sections, values are dictionaries of fields) and can make - any change to it. *optional*, *multi* - - Example:: - - [global] - setup_hooks = _setuphooks.customize_config - - - -.. _setupcfg-section-metadata: - -Metadata --------- - -The metadata section contains the metadata for the project as described in -:PEP:`345`. Field names are case-insensitive. - -Fields: - -name - Name of the project. - -version - Version of the project. Must comply with :PEP:`386`. - -platform - Platform specification describing an operating system - supported by the distribution which is not listed in the "Operating System" - Trove classifiers (:PEP:`301`). *optional*, *multi* - -supported-platform - Binary distributions containing a PKG-INFO file will - use the Supported-Platform field in their metadata to specify the OS and - CPU for which the binary distribution was compiled. The semantics of - the Supported-Platform field are free form. *optional*, *multi* - -summary - A one-line summary of what the distribution does. - (Used to be called *description* in Distutils1.) - -description - A longer description. (Used to be called *long_description* - in Distutils1.) A file can be provided in the *description-file* field. - *optional* - -keywords - A list of additional keywords to be used to assist searching - for the distribution in a larger catalog. Comma or space-separated. - *optional* - -home-page - The URL for the distribution's home page. - -download-url - The URL from which this version of the distribution - can be downloaded. *optional* - -author - Author's name. *optional* - -author-email - Author's e-mail. *optional* - -maintainer - Maintainer's name. *optional* - -maintainer-email - Maintainer's e-mail. *optional* - -license - A text indicating the term of uses, when a trove classifier does - not match. *optional*. - -classifiers - Classification for the distribution, as described in PEP 301. - *optional*, *multi*, *environ* - -requires-dist - name of another packaging project required as a dependency. - The format is *name (version)* where version is an optional - version declaration, as described in PEP 345. *optional*, *multi*, *environ* - -provides-dist - name of another packaging project contained within this - distribution. Same format than *requires-dist*. *optional*, *multi*, - *environ* - -obsoletes-dist - name of another packaging project this version obsoletes. - Same format than *requires-dist*. *optional*, *multi*, *environ* - -requires-python - Specifies the Python version the distribution requires. The value is a - comma-separated list of version predicates, as described in PEP 345. - *optional*, *environ* - -requires-externals - a dependency in the system. This field is free-form, - and just a hint for downstream maintainers. *optional*, *multi*, - *environ* - -project-url - A label, followed by a browsable URL for the project. - "label, url". The label is limited to 32 signs. *optional*, *multi* - -One extra field not present in PEP 345 is supported: - -description-file - Path to a text file that will be used to fill the ``description`` field. - Multiple values are accepted; they must be separated by whitespace. - ``description-file`` and ``description`` are mutually exclusive. *optional* - - - -Example:: - - [metadata] - name = pypi2rpm - version = 0.1 - author = Tarek Ziadé - author-email = tarek@ziade.org - summary = Script that transforms an sdist archive into a RPM package - description-file = README - home-page = http://bitbucket.org/tarek/pypi2rpm/wiki/Home - project-url: - Repository, http://bitbucket.org/tarek/pypi2rpm/ - RSS feed, https://bitbucket.org/tarek/pypi2rpm/rss - classifier = - Development Status :: 3 - Alpha - License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1) - -You should not give any explicit value for metadata-version: it will be guessed -from the fields present in the file. - - -.. _setupcfg-section-files: - -Files ------ - -This section describes the files included in the project. - -packages_root - the root directory containing all packages and modules - (default: current directory, i.e. the project's top-level - directory where :file:`setup.cfg` lives). *optional* - -packages - a list of packages the project includes *optional*, *multi* - -modules - a list of packages the project includes *optional*, *multi* - -scripts - a list of scripts the project includes *optional*, *multi* - -extra_files - a list of patterns for additional files to include in source distributions - (see :ref:`packaging-manifest`) *optional*, *multi* - -Example:: - - [files] - packages_root = src - packages = - pypi2rpm - pypi2rpm.command - - scripts = - pypi2rpm/pypi2rpm.py - - extra_files = - setup.py - README - - -.. Note:: - The :file:`setup.cfg` configuration file is included by default. Contrary to - Distutils, :file:`README` (or :file:`README.txt`) and :file:`setup.py` are - not included by default. - - -Resources -^^^^^^^^^ - -This section describes the files used by the project which must not be installed -in the same place that python modules or libraries, they are called -**resources**. They are for example documentation files, script files, -databases, etc... - -For declaring resources, you must use this notation:: - - source = destination - -Data-files are declared in the **resources** field in the **file** section, for -example:: - - [files] - resources = - source1 = destination1 - source2 = destination2 - -The **source** part of the declaration are relative paths of resources files -(using unix path separator **/**). For example, if you've this source tree:: - - foo/ - doc/ - doc.man - scripts/ - foo.sh - -Your setup.cfg will look like:: - - [files] - resources = - doc/doc.man = destination_doc - scripts/foo.sh = destination_scripts - -The final paths where files will be placed are composed by : **source** + -**destination**. In the previous example, **doc/doc.man** will be placed in -**destination_doc/doc/doc.man** and **scripts/foo.sh** will be placed in -**destination_scripts/scripts/foo.sh**. (If you want more control on the final -path, take a look at :ref:`setupcfg-resources-base-prefix`). - -The **destination** part of resources declaration are paths with categories. -Indeed, it's generally a bad idea to give absolute path as it will be cross -incompatible. So, you must use resources categories in your **destination** -declaration. Categories will be replaced by their real path at the installation -time. Using categories is all benefit, your declaration will be simpler, cross -platform and it will allow packager to place resources files where they want -without breaking your code. - -Categories can be specified by using this syntax:: - - {category} - -Default categories are: - -* config -* appdata -* appdata.arch -* appdata.persistent -* appdata.disposable -* help -* icon -* scripts -* doc -* info -* man - -A special category also exists **{distribution.name}** that will be replaced by -the name of the distribution, but as most of the defaults categories use them, -so it's not necessary to add **{distribution.name}** into your destination. - -If you use categories in your declarations, and you are encouraged to do, final -path will be:: - - source + destination_expanded - -.. _example_final_path: - -For example, if you have this setup.cfg:: - - [metadata] - name = foo - - [files] - resources = - doc/doc.man = {doc} - -And if **{doc}** is replaced by **{datadir}/doc/{distribution.name}**, final -path will be:: - - {datadir}/doc/foo/doc/doc.man - -Where {datafir} category will be platform-dependent. - - -More control on source part -""""""""""""""""""""""""""" - -Glob syntax -''''''''''' - -When you declare source file, you can use a glob-like syntax to match multiples file, for example:: - - scripts/* = {script} - -Will match all the files in the scripts directory and placed them in the script category. - -Glob tokens are: - - * ``*``: match all files. - * ``?``: match any character. - * ``**``: match any level of tree recursion (even 0). - * ``{}``: will match any part separated by comma (example: ``{sh,bat}``). - -.. TODO Add examples - -Order of declaration -'''''''''''''''''''' - -The order of declaration is important if one file match multiple rules. The last -rules matched by file is used, this is useful if you have this source tree:: - - foo/ - doc/ - index.rst - setup.rst - documentation.txt - doc.tex - README - -And you want all the files in the doc directory to be placed in {doc} category, -but README must be placed in {help} category, instead of listing all the files -one by one, you can declare them in this way:: - - [files] - resources = - doc/* = {doc} - doc/README = {help} - -Exclude -''''''' - -You can exclude some files of resources declaration by giving no destination, it -can be useful if you have a non-resources file in the same directory of -resources files:: - - foo/ - doc/ - RELEASES - doc.tex - documentation.txt - docu.rst - -Your **files** section will be:: - - [files] - resources = - doc/* = {doc} - doc/RELEASES = - -More control on destination part -"""""""""""""""""""""""""""""""" - -.. _setupcfg-resources-base-prefix: - -Defining a base prefix -'''''''''''''''''''''' - -When you define your resources, you can have more control of how the final path -is computed. - -By default, the final path is:: - - destination + source - -This can generate long paths, for example (example_final_path_):: - - {datadir}/doc/foo/doc/doc.man - -When you declare your source, you can use whitespace to split the source in -**prefix** **suffix**. So, for example, if you have this source:: - - docs/ doc.man - -The **prefix** is "docs/" and the **suffix** is "doc.html". - -.. note:: - - Separator can be placed after a path separator or replace it. So these two - sources are equivalent:: - - docs/ doc.man - docs doc.man - -.. note:: - - Glob syntax is working the same way with standard source and split source. - So these rules:: - - docs/* - docs/ * - docs * - - Will match all the files in the docs directory. - -When you use split source, the final path is computed this way:: - - destination + prefix - -So for example, if you have this setup.cfg:: - - [metadata] - name = foo - - [files] - resources = - doc/ doc.man = {doc} - -And if **{doc}** is replaced by **{datadir}/doc/{distribution.name}**, final -path will be:: - - {datadir}/doc/foo/doc.man - - -Overwriting paths for categories -"""""""""""""""""""""""""""""""" - -This part is intended for system administrators or downstream OS packagers. - -The real paths of categories are registered in the *sysconfig.cfg* file -installed in your python installation. This file uses an ini format too. -The content of the file is organized into several sections: - -* globals: Standard categories's paths. -* posix_prefix: Standard paths for categories and installation paths for posix - system. -* other ones XXX - -Standard categories paths are platform independent, they generally refers to -other categories, which are platform dependent. :mod:`sysconfig` will choose -these category from sections matching os.name. For example:: - - doc = {datadir}/doc/{distribution.name} - -It refers to datadir category, which can be different between platforms. In -posix system, it may be:: - - datadir = /usr/share - -So the final path will be:: - - doc = /usr/share/doc/{distribution.name} - -The platform-dependent categories are: - -* confdir -* datadir -* libdir -* base - - -Defining extra categories -""""""""""""""""""""""""" - -.. TODO - - -Examples -"""""""" - -These examples are incremental but work unitarily. - -Resources in root dir -''''''''''''''''''''' - -Source tree:: - - babar-1.0/ - README - babar.sh - launch.sh - babar.py - -:file:`setup.cfg`:: - - [files] - resources = - README = {doc} - *.sh = {scripts} - -So babar.sh and launch.sh will be placed in {scripts} directory. - -Now let's move all the scripts into a scripts directory. - -Resources in sub-directory -'''''''''''''''''''''''''' - -Source tree:: - - babar-1.1/ - README - scripts/ - babar.sh - launch.sh - LAUNCH - babar.py - -:file:`setup.cfg`:: - - [files] - resources = - README = {doc} - scripts/ LAUNCH = {doc} - scripts/ *.sh = {scripts} - -It's important to use the separator after scripts/ to install all the shell -scripts into {scripts} instead of {scripts}/scripts. - -Now let's add some docs. - -Resources in multiple sub-directories -''''''''''''''''''''''''''''''''''''' - -Source tree:: - - babar-1.2/ - README - scripts/ - babar.sh - launch.sh - LAUNCH - docs/ - api - man - babar.py - -:file:`setup.cfg`:: - - [files] - resources = - README = {doc} - scripts/ LAUNCH = {doc} - scripts/ *.sh = {scripts} - doc/ * = {doc} - doc/ man = {man} - -You want to place all the file in the docs script into {doc} category, instead -of man, which must be placed into {man} category, we will use the order of -declaration of globs to choose the destination, the last glob that match the -file is used. - -Now let's add some scripts for windows users. - -Complete example -'''''''''''''''' - -Source tree:: - - babar-1.3/ - README - doc/ - api - man - scripts/ - babar.sh - launch.sh - babar.bat - launch.bat - LAUNCH - -:file:`setup.cfg`:: - - [files] - resources = - README = {doc} - scripts/ LAUNCH = {doc} - scripts/ *.{sh,bat} = {scripts} - doc/ * = {doc} - doc/ man = {man} - -We use brace expansion syntax to place all the shell and batch scripts into -{scripts} category. - - -.. _setupcfg-section-extensions: - -Extension modules sections --------------------------- - -If a project includes extension modules written in C or C++, each one of them -needs to have its options defined in a dedicated section. Here's an example:: - - [files] - packages = coconut - - [extension: coconut._fastcoconut] - language = cxx - sources = cxx_src/cononut_utils.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' - -The section name must start with ``extension:``; the right-hand part is used as -the full name (including a parent package, if any) of the extension. Whitespace -around the extension name is allowed. If the extension module is not standalone -(e.g. ``_bisect``) but part of a package (e.g. ``thing._speedups``), the parent -package must be listed in the ``packages`` field. -Valid fields and their values are listed in the documentation of the -:class:`packaging.compiler.extension.Extension` class; values documented as -Python lists translate to multi-line values in the configuration file. In -addition, multi-line values accept environment markers on each line, after a -``--``. - - -.. _setupcfg-section-commands: - -Commands sections ------------------ - -To pass options to commands without having to type them on the command line -for each invocation, you can write them in the :file:`setup.cfg` file, in a -section named after the command. Example:: - - [sdist] - # special function to add custom files - manifest-builders = package.setup.list_extra_files - - [build] - use-2to3 = True - - [build_ext] - inplace = on - - [check] - strict = on - all = on - -Option values given in the configuration file can be overriden on the command -line. See :ref:`packaging-setup-config` for more information. - -These sections are also used to define :ref:`command hooks -`. - - -.. _setupcfg-extensibility: - -Extensibility -============= - -Every section can have fields that are not part of this specification. They are -called **extensions**. - -An extension field starts with ``X-``. Example:: - - [metadata] - name = Distribute - X-Debian-Name = python-distribute - - -.. _setupcfg-changes: - -Changes in the specification -============================ - -The versioning scheme for this specification is **MAJOR.MINOR**. Changes in the -specification will cause the version number to be updated. - -Changes to the minor number reflect backwards-compatible changes: - -- New fields and sections (optional or mandatory) can be added. -- Optional fields can be removed. - -The major number will be incremented for backwards-incompatible changes: - -- Mandatory fields or sections are removed. -- Fields change their meaning. - -As a consequence, a tool written to consume 1.5 has these properties: - -- Can read 1.1, 1.2 and all versions < 1.5, since the tool knows what - optional fields weren't there. - - .. XXX clarify - -- Can also read 1.6 and other 1.x versions: The tool will just ignore fields it - doesn't know about, even if they are mandatory in the new version. If - optional fields were removed, the tool will just consider them absent. - -- Cannot read 2.x and should refuse to interpret such files. - -A tool written to produce 1.x should have these properties: - -- Writes all mandatory fields. -- May write optional fields. - - -.. _setupcfg-acks: - -Acknowledgments -=============== - -This specification includes work and feedback from these people: - -- Tarek Ziadé -- Julien Jehannet -- Boris Feld -- Éric Araujo - -(If your name is missing, please :ref:`let us know `.) diff --git a/Doc/packaging/setupscript.rst b/Doc/packaging/setupscript.rst deleted file mode 100644 index cafde20e548f..000000000000 --- a/Doc/packaging/setupscript.rst +++ /dev/null @@ -1,693 +0,0 @@ -.. _packaging-setup-script: - -************************ -Writing the Setup Script -************************ - -The setup script is the center of all activity in building, distributing, and -installing modules using Distutils. The main purpose of the setup script is -to describe your module distribution to Distutils, so that the various -commands that operate on your modules do the right thing. As we saw in section -:ref:`packaging-simple-example`, the setup script consists mainly of a -call to :func:`setup` where the most information is supplied as -keyword arguments to :func:`setup`. - -Here's a slightly more involved example, which we'll follow for the next couple -of sections: a setup script that could be used for Packaging itself:: - - #!/usr/bin/env python - - from packaging.core import setup, find_packages - - setup(name='Packaging', - version='1.0', - summary='Python Distribution Utilities', - keywords=['packaging', 'packaging'], - author=u'Tarek Ziadé', - author_email='tarek@ziade.org', - home_page='http://bitbucket.org/tarek/packaging/wiki/Home', - license='PSF', - packages=find_packages()) - - -There are only two differences between this and the trivial one-file -distribution presented in section :ref:`packaging-simple-example`: more -metadata and the specification of pure Python modules by package rather than -by module. This is important since Ristutils consist of a couple of dozen -modules split into (so far) two packages; an explicit list of every module -would be tedious to generate and difficult to maintain. For more information -on the additional metadata, see section :ref:`packaging-metadata`. - -Note that any pathnames (files or directories) supplied in the setup script -should be written using the Unix convention, i.e. slash-separated. The -Distutils will take care of converting this platform-neutral representation into -whatever is appropriate on your current platform before actually using the -pathname. This makes your setup script portable across operating systems, which -of course is one of the major goals of the Distutils. In this spirit, all -pathnames in this document are slash-separated. - -This, of course, only applies to pathnames given to Distutils functions. If -you, for example, use standard Python functions such as :func:`glob.glob` or -:func:`os.listdir` to specify files, you should be careful to write portable -code instead of hardcoding path separators:: - - glob.glob(os.path.join('mydir', 'subdir', '*.html')) - os.listdir(os.path.join('mydir', 'subdir')) - - -.. _packaging-listing-packages: - -Listing whole packages -====================== - -The :option:`packages` option tells the Distutils to process (build, distribute, -install, etc.) all pure Python modules found in each package mentioned in the -:option:`packages` list. In order to do this, of course, there has to be a -correspondence between package names and directories in the filesystem. The -default correspondence is the most obvious one, i.e. package :mod:`packaging` is -found in the directory :file:`packaging` relative to the distribution root. -Thus, when you say ``packages = ['foo']`` in your setup script, you are -promising that the Distutils will find a file :file:`foo/__init__.py` (which -might be spelled differently on your system, but you get the idea) relative to -the directory where your setup script lives. If you break this promise, the -Distutils will issue a warning but still process the broken package anyway. - -If you use a different convention to lay out your source directory, that's no -problem: you just have to supply the :option:`package_dir` option to tell the -Distutils about your convention. For example, say you keep all Python source -under :file:`lib`, so that modules in the "root package" (i.e., not in any -package at all) are in :file:`lib`, modules in the :mod:`foo` package are in -:file:`lib/foo`, and so forth. Then you would put :: - - package_dir = {'': 'lib'} - -in your setup script. The keys to this dictionary are package names, and an -empty package name stands for the root package. The values are directory names -relative to your distribution root. In this case, when you say ``packages = -['foo']``, you are promising that the file :file:`lib/foo/__init__.py` exists. - -Another possible convention is to put the :mod:`foo` package right in -:file:`lib`, the :mod:`foo.bar` package in :file:`lib/bar`, etc. This would be -written in the setup script as :: - - package_dir = {'foo': 'lib'} - -A ``package: dir`` entry in the :option:`package_dir` dictionary implicitly -applies to all packages below *package*, so the :mod:`foo.bar` case is -automatically handled here. In this example, having ``packages = ['foo', -'foo.bar']`` tells the Distutils to look for :file:`lib/__init__.py` and -:file:`lib/bar/__init__.py`. (Keep in mind that although :option:`package_dir` -applies recursively, you must explicitly list all packages in -:option:`packages`: the Distutils will *not* recursively scan your source tree -looking for any directory with an :file:`__init__.py` file.) - - -.. _packaging-listing-modules: - -Listing individual modules -========================== - -For a small module distribution, you might prefer to list all modules rather -than listing packages---especially the case of a single module that goes in the -"root package" (i.e., no package at all). This simplest case was shown in -section :ref:`packaging-simple-example`; here is a slightly more involved -example:: - - py_modules = ['mod1', 'pkg.mod2'] - -This describes two modules, one of them in the "root" package, the other in the -:mod:`pkg` package. Again, the default package/directory layout implies that -these two modules can be found in :file:`mod1.py` and :file:`pkg/mod2.py`, and -that :file:`pkg/__init__.py` exists as well. And again, you can override the -package/directory correspondence using the :option:`package_dir` option. - - -.. _packaging-describing-extensions: - -Describing extension modules -============================ - -Just as writing Python extension modules is a bit more complicated than writing -pure Python modules, describing them to the Distutils is a bit more complicated. -Unlike pure modules, it's not enough just to list modules or packages and expect -the Distutils to go out and find the right files; you have to specify the -extension name, source file(s), and any compile/link requirements (include -directories, libraries to link with, etc.). - -.. XXX read over this section - -All of this is done through another keyword argument to :func:`setup`, the -:option:`ext_modules` option. :option:`ext_modules` is just a list of -:class:`Extension` instances, each of which describes a single extension module. -Suppose your distribution includes a single extension, called :mod:`foo` and -implemented by :file:`foo.c`. If no additional instructions to the -compiler/linker are needed, describing this extension is quite simple:: - - Extension('foo', ['foo.c']) - -The :class:`Extension` class can be imported from :mod:`packaging.core` along -with :func:`setup`. Thus, the setup script for a module distribution that -contains only this one extension and nothing else might be:: - - from packaging.core import setup, Extension - setup(name='foo', - version='1.0', - ext_modules=[Extension('foo', ['foo.c'])]) - -The :class:`Extension` class (actually, the underlying extension-building -machinery implemented by the :command:`build_ext` command) supports a great deal -of flexibility in describing Python extensions, which is explained in the -following sections. - - -Extension names and packages ----------------------------- - -The first argument to the :class:`Extension` constructor is always the name of -the extension, including any package names. For example, :: - - Extension('foo', ['src/foo1.c', 'src/foo2.c']) - -describes an extension that lives in the root package, while :: - - Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c']) - -describes the same extension in the :mod:`pkg` package. The source files and -resulting object code are identical in both cases; the only difference is where -in the filesystem (and therefore where in Python's namespace hierarchy) the -resulting extension lives. - -If your distribution contains only one or more extension modules in a package, -you need to create a :file:`{package}/__init__.py` file anyway, otherwise Python -won't be able to import anything. - -If you have a number of extensions all in the same package (or all under the -same base package), use the :option:`ext_package` keyword argument to -:func:`setup`. For example, :: - - setup(..., - ext_package='pkg', - ext_modules=[Extension('foo', ['foo.c']), - Extension('subpkg.bar', ['bar.c'])]) - -will compile :file:`foo.c` to the extension :mod:`pkg.foo`, and :file:`bar.c` to -:mod:`pkg.subpkg.bar`. - - -Extension source files ----------------------- - -The second argument to the :class:`Extension` constructor is a list of source -files. Since the Distutils currently only support C, C++, and Objective-C -extensions, these are normally C/C++/Objective-C source files. (Be sure to use -appropriate extensions to distinguish C++\ source files: :file:`.cc` and -:file:`.cpp` seem to be recognized by both Unix and Windows compilers.) - -However, you can also include SWIG interface (:file:`.i`) files in the list; the -:command:`build_ext` command knows how to deal with SWIG extensions: it will run -SWIG on the interface file and compile the resulting C/C++ file into your -extension. - -.. XXX SWIG support is rough around the edges and largely untested! - -This warning notwithstanding, options to SWIG can be currently passed like -this:: - - setup(..., - ext_modules=[Extension('_foo', ['foo.i'], - swig_opts=['-modern', '-I../include'])], - py_modules=['foo']) - -Or on the command line like this:: - - > python setup.py build_ext --swig-opts="-modern -I../include" - -On some platforms, you can include non-source files that are processed by the -compiler and included in your extension. Currently, this just means Windows -message text (:file:`.mc`) files and resource definition (:file:`.rc`) files for -Visual C++. These will be compiled to binary resource (:file:`.res`) files and -linked into the executable. - - -Preprocessor options --------------------- - -Three optional arguments to :class:`Extension` will help if you need to specify -include directories to search or preprocessor macros to define/undefine: -``include_dirs``, ``define_macros``, and ``undef_macros``. - -For example, if your extension requires header files in the :file:`include` -directory under your distribution root, use the ``include_dirs`` option:: - - Extension('foo', ['foo.c'], include_dirs=['include']) - -You can specify absolute directories there; if you know that your extension will -only be built on Unix systems with X11R6 installed to :file:`/usr`, you can get -away with :: - - Extension('foo', ['foo.c'], include_dirs=['/usr/include/X11']) - -You should avoid this sort of non-portable usage if you plan to distribute your -code: it's probably better to write C code like :: - - #include - -If you need to include header files from some other Python extension, you can -take advantage of the fact that header files are installed in a consistent way -by the Distutils :command:`install_header` command. For example, the Numerical -Python header files are installed (on a standard Unix installation) to -:file:`/usr/local/include/python1.5/Numerical`. (The exact location will differ -according to your platform and Python installation.) Since the Python include -directory---\ :file:`/usr/local/include/python1.5` in this case---is always -included in the search path when building Python extensions, the best approach -is to write C code like :: - - #include - -.. TODO check if it's d2.sysconfig or the new sysconfig module now - -If you must put the :file:`Numerical` include directory right into your header -search path, though, you can find that directory using the Distutils -:mod:`packaging.sysconfig` module:: - - from packaging.sysconfig import get_python_inc - incdir = os.path.join(get_python_inc(plat_specific=1), 'Numerical') - setup(..., - Extension(..., include_dirs=[incdir])) - -Even though this is quite portable---it will work on any Python installation, -regardless of platform---it's probably easier to just write your C code in the -sensible way. - -You can define and undefine preprocessor macros with the ``define_macros`` and -``undef_macros`` options. ``define_macros`` takes a list of ``(name, value)`` -tuples, where ``name`` is the name of the macro to define (a string) and -``value`` is its value: either a string or ``None``. (Defining a macro ``FOO`` -to ``None`` is the equivalent of a bare ``#define FOO`` in your C source: with -most compilers, this sets ``FOO`` to the string ``1``.) ``undef_macros`` is -just a list of macros to undefine. - -For example:: - - Extension(..., - define_macros=[('NDEBUG', '1'), - ('HAVE_STRFTIME', None)], - undef_macros=['HAVE_FOO', 'HAVE_BAR']) - -is the equivalent of having this at the top of every C source file:: - - #define NDEBUG 1 - #define HAVE_STRFTIME - #undef HAVE_FOO - #undef HAVE_BAR - - -Library options ---------------- - -You can also specify the libraries to link against when building your extension, -and the directories to search for those libraries. The ``libraries`` option is -a list of libraries to link against, ``library_dirs`` is a list of directories -to search for libraries at link-time, and ``runtime_library_dirs`` is a list of -directories to search for shared (dynamically loaded) libraries at run-time. - -For example, if you need to link against libraries known to be in the standard -library search path on target systems :: - - Extension(..., - libraries=['gdbm', 'readline']) - -If you need to link with libraries in a non-standard location, you'll have to -include the location in ``library_dirs``:: - - Extension(..., - library_dirs=['/usr/X11R6/lib'], - libraries=['X11', 'Xt']) - -(Again, this sort of non-portable construct should be avoided if you intend to -distribute your code.) - -.. XXX Should mention clib libraries here or somewhere else! - - -Other options -------------- - -There are still some other options which can be used to handle special cases. - -The :option:`optional` option is a boolean; if it is true, -a build failure in the extension will not abort the build process, but -instead simply not install the failing extension. - -The :option:`extra_objects` option is a list of object files to be passed to the -linker. These files must not have extensions, as the default extension for the -compiler is used. - -:option:`extra_compile_args` and :option:`extra_link_args` can be used to -specify additional command-line options for the respective compiler and linker -command lines. - -:option:`export_symbols` is only useful on Windows. It can contain a list of -symbols (functions or variables) to be exported. This option is not needed when -building compiled extensions: Distutils will automatically add ``initmodule`` -to the list of exported symbols. - -The :option:`depends` option is a list of files that the extension depends on -(for example header files). The build command will call the compiler on the -sources to rebuild extension if any on this files has been modified since the -previous build. - -Relationships between Distributions and Packages -================================================ - -.. FIXME rewrite to update to PEP 345 (but without dist/release confusion) - -A distribution may relate to packages in three specific ways: - -#. It can require packages or modules. - -#. It can provide packages or modules. - -#. It can obsolete packages or modules. - -These relationships can be specified using keyword arguments to the -:func:`packaging.core.setup` function. - -Dependencies on other Python modules and packages can be specified by supplying -the *requires* keyword argument to :func:`setup`. The value must be a list of -strings. Each string specifies a package that is required, and optionally what -versions are sufficient. - -To specify that any version of a module or package is required, the string -should consist entirely of the module or package name. Examples include -``'mymodule'`` and ``'xml.parsers.expat'``. - -If specific versions are required, a sequence of qualifiers can be supplied in -parentheses. Each qualifier may consist of a comparison operator and a version -number. The accepted comparison operators are:: - - < > == - <= >= != - -These can be combined by using multiple qualifiers separated by commas (and -optional whitespace). In this case, all of the qualifiers must be matched; a -logical AND is used to combine the evaluations. - -Let's look at a bunch of examples: - -+-------------------------+----------------------------------------------+ -| Requires Expression | Explanation | -+=========================+==============================================+ -| ``==1.0`` | Only version ``1.0`` is compatible | -+-------------------------+----------------------------------------------+ -| ``>1.0, !=1.5.1, <2.0`` | Any version after ``1.0`` and before ``2.0`` | -| | is compatible, except ``1.5.1`` | -+-------------------------+----------------------------------------------+ - -Now that we can specify dependencies, we also need to be able to specify what we -provide that other distributions can require. This is done using the *provides* -keyword argument to :func:`setup`. The value for this keyword is a list of -strings, each of which names a Python module or package, and optionally -identifies the version. If the version is not specified, it is assumed to match -that of the distribution. - -Some examples: - -+---------------------+----------------------------------------------+ -| Provides Expression | Explanation | -+=====================+==============================================+ -| ``mypkg`` | Provide ``mypkg``, using the distribution | -| | version | -+---------------------+----------------------------------------------+ -| ``mypkg (1.1)`` | Provide ``mypkg`` version 1.1, regardless of | -| | the distribution version | -+---------------------+----------------------------------------------+ - -A package can declare that it obsoletes other packages using the *obsoletes* -keyword argument. The value for this is similar to that of the *requires* -keyword: a list of strings giving module or package specifiers. Each specifier -consists of a module or package name optionally followed by one or more version -qualifiers. Version qualifiers are given in parentheses after the module or -package name. - -The versions identified by the qualifiers are those that are obsoleted by the -distribution being described. If no qualifiers are given, all versions of the -named module or package are understood to be obsoleted. - -.. _packaging-installing-scripts: - -Installing Scripts -================== - -So far we have been dealing with pure and non-pure Python modules, which are -usually not run by themselves but imported by scripts. - -Scripts are files containing Python source code, intended to be started from the -command line. Scripts don't require Distutils to do anything very complicated. -The only clever feature is that if the first line of the script starts with -``#!`` and contains the word "python", the Distutils will adjust the first line -to refer to the current interpreter location. By default, it is replaced with -the current interpreter location. The :option:`--executable` (or :option:`-e`) -option will allow the interpreter path to be explicitly overridden. - -The :option:`scripts` option simply is a list of files to be handled in this -way. From the PyXML setup script:: - - setup(..., - scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']) - -All the scripts will also be added to the ``MANIFEST`` file if no template is -provided. See :ref:`packaging-manifest`. - -.. _packaging-installing-package-data: - -Installing Package Data -======================= - -Often, additional files need to be installed into a package. These files are -often data that's closely related to the package's implementation, or text files -containing documentation that might be of interest to programmers using the -package. These files are called :dfn:`package data`. - -Package data can be added to packages using the ``package_data`` keyword -argument to the :func:`setup` function. The value must be a mapping from -package name to a list of relative path names that should be copied into the -package. The paths are interpreted as relative to the directory containing the -package (information from the ``package_dir`` mapping is used if appropriate); -that is, the files are expected to be part of the package in the source -directories. They may contain glob patterns as well. - -The path names may contain directory portions; any necessary directories will be -created in the installation. - -For example, if a package should contain a subdirectory with several data files, -the files can be arranged like this in the source tree:: - - setup.py - src/ - mypkg/ - __init__.py - module.py - data/ - tables.dat - spoons.dat - forks.dat - -The corresponding call to :func:`setup` might be:: - - setup(..., - packages=['mypkg'], - package_dir={'mypkg': 'src/mypkg'}, - package_data={'mypkg': ['data/*.dat']}) - - -All the files that match ``package_data`` will be added to the ``MANIFEST`` -file if no template is provided. See :ref:`packaging-manifest`. - - -.. _packaging-additional-files: - -Installing Additional Files -=========================== - -The :option:`data_files` option can be used to specify additional files needed -by the module distribution: configuration files, message catalogs, data files, -anything which doesn't fit in the previous categories. - -:option:`data_files` specifies a sequence of (*directory*, *files*) pairs in the -following way:: - - setup(..., - data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']), - ('config', ['cfg/data.cfg']), - ('/etc/init.d', ['init-script'])]) - -Note that you can specify the directory names where the data files will be -installed, but you cannot rename the data files themselves. - -Each (*directory*, *files*) pair in the sequence specifies the installation -directory and the files to install there. If *directory* is a relative path, it -is interpreted relative to the installation prefix (Python's ``sys.prefix`` for -pure-Python packages, ``sys.exec_prefix`` for packages that contain extension -modules). Each file name in *files* is interpreted relative to the -:file:`setup.py` script at the top of the package source distribution. No -directory information from *files* is used to determine the final location of -the installed file; only the name of the file is used. - -You can specify the :option:`data_files` options as a simple sequence of files -without specifying a target directory, but this is not recommended, and the -:command:`install_dist` command will print a warning in this case. To install data -files directly in the target directory, an empty string should be given as the -directory. - -All the files that match ``data_files`` will be added to the ``MANIFEST`` file -if no template is provided. See :ref:`packaging-manifest`. - - - -.. _packaging-metadata: - -Metadata reference -================== - -The setup script may include additional metadata beyond the name and version. -This table describes required and additional information: - -.. TODO synchronize with setupcfg; link to it (but don't remove it, it's a - useful summary) - -+----------------------+---------------------------+-----------------+--------+ -| Meta-Data | Description | Value | Notes | -+======================+===========================+=================+========+ -| ``name`` | name of the project | short string | \(1) | -+----------------------+---------------------------+-----------------+--------+ -| ``version`` | version of this release | short string | (1)(2) | -+----------------------+---------------------------+-----------------+--------+ -| ``author`` | project author's name | short string | \(3) | -+----------------------+---------------------------+-----------------+--------+ -| ``author_email`` | email address of the | email address | \(3) | -| | project author | | | -+----------------------+---------------------------+-----------------+--------+ -| ``maintainer`` | project maintainer's name | short string | \(3) | -+----------------------+---------------------------+-----------------+--------+ -| ``maintainer_email`` | email address of the | email address | \(3) | -| | project maintainer | | | -+----------------------+---------------------------+-----------------+--------+ -| ``home_page`` | home page for the project | URL | \(1) | -+----------------------+---------------------------+-----------------+--------+ -| ``summary`` | short description of the | short string | | -| | project | | | -+----------------------+---------------------------+-----------------+--------+ -| ``description`` | longer description of the | long string | \(5) | -| | project | | | -+----------------------+---------------------------+-----------------+--------+ -| ``download_url`` | location where the | URL | | -| | project may be downloaded | | | -+----------------------+---------------------------+-----------------+--------+ -| ``classifiers`` | a list of classifiers | list of strings | \(4) | -+----------------------+---------------------------+-----------------+--------+ -| ``platforms`` | a list of platforms | list of strings | | -+----------------------+---------------------------+-----------------+--------+ -| ``license`` | license for the release | short string | \(6) | -+----------------------+---------------------------+-----------------+--------+ - -Notes: - -(1) - These fields are required. - -(2) - It is recommended that versions take the form *major.minor[.patch[.sub]]*. - -(3) - Either the author or the maintainer must be identified. - -(4) - The list of classifiers is available from the `PyPI website - `_. See also :mod:`packaging.create`. - -(5) - The ``description`` field is used by PyPI when you are registering a - release, to build its PyPI page. - -(6) - The ``license`` field is a text indicating the license covering the - distribution where the license is not a selection from the "License" Trove - classifiers. See the ``Classifier`` field. Notice that - there's a ``licence`` distribution option which is deprecated but still - acts as an alias for ``license``. - -'short string' - A single line of text, not more than 200 characters. - -'long string' - Multiple lines of plain text in reStructuredText format (see - http://docutils.sf.net/). - -'list of strings' - See below. - -In Python 2.x, "string value" means a unicode object. If a byte string (str or -bytes) is given, it has to be valid ASCII. - -.. TODO move this section to the version document, keep a summary, add a link - -Encoding the version information is an art in itself. Python projects generally -adhere to the version format *major.minor[.patch][sub]*. The major number is 0 -for initial, experimental releases of software. It is incremented for releases -that represent major milestones in a project. The minor number is incremented -when important new features are added to the project. The patch number -increments when bug-fix releases are made. Additional trailing version -information is sometimes used to indicate sub-releases. These are -"a1,a2,...,aN" (for alpha releases, where functionality and API may change), -"b1,b2,...,bN" (for beta releases, which only fix bugs) and "pr1,pr2,...,prN" -(for final pre-release release testing). Some examples: - -0.1.0 - the first, experimental release of a project - -1.0.1a2 - the second alpha release of the first patch version of 1.0 - -:option:`classifiers` are specified in a Python list:: - - setup(..., - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Python Software Foundation License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: Python', - 'Topic :: Communications :: Email', - 'Topic :: Office/Business', - 'Topic :: Software Development :: Bug Tracking', - ]) - - -Debugging the setup script -========================== - -Sometimes things go wrong, and the setup script doesn't do what the developer -wants. - -Distutils catches any exceptions when running the setup script, and print a -simple error message before the script is terminated. The motivation for this -behaviour is to not confuse administrators who don't know much about Python and -are trying to install a project. If they get a big long traceback from deep -inside the guts of Distutils, they may think the project or the Python -installation is broken because they don't read all the way down to the bottom -and see that it's a permission problem. - -.. FIXME DISTUTILS_DEBUG is dead, document logging/warnings here - -On the other hand, this doesn't help the developer to find the cause of the -failure. For this purpose, the DISTUTILS_DEBUG environment variable can be set -to anything except an empty string, and Packaging will now print detailed -information about what it is doing, and prints the full traceback in case an -exception occurs. diff --git a/Doc/packaging/sourcedist.rst b/Doc/packaging/sourcedist.rst deleted file mode 100644 index 2cedc15ea90c..000000000000 --- a/Doc/packaging/sourcedist.rst +++ /dev/null @@ -1,266 +0,0 @@ -.. _packaging-source-dist: - -****************************** -Creating a Source Distribution -****************************** - -As shown in section :ref:`packaging-simple-example`, you use the :command:`sdist` command -to create a source distribution. In the simplest case, :: - - python setup.py sdist - -(assuming you haven't specified any :command:`sdist` options in the setup script -or config file), :command:`sdist` creates the archive of the default format for -the current platform. The default format is a gzip'ed tar file -(:file:`.tar.gz`) on Unix, and ZIP file on Windows. - -You can specify as many formats as you like using the :option:`--formats` -option, for example:: - - python setup.py sdist --formats=gztar,zip - -to create a gzipped tarball and a zip file. The available formats are: - -+-----------+-------------------------+---------+ -| Format | Description | Notes | -+===========+=========================+=========+ -| ``zip`` | zip file (:file:`.zip`) | (1),(3) | -+-----------+-------------------------+---------+ -| ``gztar`` | gzip'ed tar file | \(2) | -| | (:file:`.tar.gz`) | | -+-----------+-------------------------+---------+ -| ``bztar`` | bzip2'ed tar file | | -| | (:file:`.tar.bz2`) | | -+-----------+-------------------------+---------+ -| ``tar`` | tar file (:file:`.tar`) | | -+-----------+-------------------------+---------+ - -Notes: - -(1) - default on Windows - -(2) - default on Unix - -(3) - requires either external :program:`zip` utility or :mod:`zipfile` module (part - of the standard Python library since Python 1.6) - -When using any ``tar`` format (``gztar``, ``bztar`` or -``tar``) under Unix, you can specify the ``owner`` and ``group`` names -that will be set for each member of the archive. - -For example, if you want all files of the archive to be owned by root:: - - python setup.py sdist --owner=root --group=root - - -.. _packaging-manifest: - -Specifying the files to distribute -================================== - -If you don't supply an explicit list of files (or instructions on how to -generate one), the :command:`sdist` command puts a minimal default set into the -source distribution: - -* all Python source files implied by the :option:`py_modules` and - :option:`packages` options - -* all C source files mentioned in the :option:`ext_modules` or - :option:`libraries` options - -* scripts identified by the :option:`scripts` option - See :ref:`packaging-installing-scripts`. - -* anything that looks like a test script: :file:`test/test\*.py` (currently, the - Packaging don't do anything with test scripts except include them in source - distributions, but in the future there will be a standard for testing Python - module distributions) - -* the configuration file :file:`setup.cfg` - -* all files that matches the ``package_data`` metadata. - See :ref:`packaging-installing-package-data`. - -* all files that matches the ``data_files`` metadata. - See :ref:`packaging-additional-files`. - -Contrary to Distutils, :file:`README` (or :file:`README.txt`) and -:file:`setup.py` are not included by default. - -Sometimes this is enough, but usually you will want to specify additional files -to distribute. The typical way to do this is to write a *manifest template*, -called :file:`MANIFEST.in` by default. The manifest template is just a list of -instructions for how to generate your manifest file, :file:`MANIFEST`, which is -the exact list of files to include in your source distribution. The -:command:`sdist` command processes this template and generates a manifest based -on its instructions and what it finds in the filesystem. - -If you prefer to roll your own manifest file, the format is simple: one filename -per line, regular files (or symlinks to them) only. If you do supply your own -:file:`MANIFEST`, you must specify everything: the default set of files -described above does not apply in this case. - -:file:`MANIFEST` files start with a comment indicating they are generated. -Files without this comment are not overwritten or removed. - -See :ref:`packaging-manifest-template` section for a syntax reference. - - -.. _packaging-manifest-options: - -Manifest-related options -======================== - -The normal course of operations for the :command:`sdist` command is as follows: - -* if the manifest file, :file:`MANIFEST` doesn't exist, read :file:`MANIFEST.in` - and create the manifest - -* if neither :file:`MANIFEST` nor :file:`MANIFEST.in` exist, create a manifest - with just the default file set - -* if either :file:`MANIFEST.in` or the setup script (:file:`setup.py`) are more - recent than :file:`MANIFEST`, recreate :file:`MANIFEST` by reading - :file:`MANIFEST.in` - -* use the list of files now in :file:`MANIFEST` (either just generated or read - in) to create the source distribution archive(s) - -There are a couple of options that modify this behaviour. First, use the -:option:`--no-defaults` and :option:`--no-prune` to disable the standard -"include" and "exclude" sets. - -Second, you might just want to (re)generate the manifest, but not create a -source distribution:: - - python setup.py sdist --manifest-only - -:option:`-o` is a shortcut for :option:`--manifest-only`. - - -.. _packaging-manifest-template: - -The MANIFEST.in template -======================== - -A :file:`MANIFEST.in` file can be added in a project to define the list of -files to include in the distribution built by the :command:`sdist` command. - -When :command:`sdist` is run, it will look for the :file:`MANIFEST.in` file -and interpret it to generate the :file:`MANIFEST` file that contains the -list of files that will be included in the package. - -This mechanism can be used when the default list of files is not enough. -(See :ref:`packaging-manifest`). - -Principle ---------- - -The manifest template has one command per line, where each command specifies a -set of files to include or exclude from the source distribution. For an -example, let's look at the Packaging' own manifest template:: - - include *.txt - recursive-include examples *.txt *.py - prune examples/sample?/build - -The meanings should be fairly clear: include all files in the distribution root -matching :file:`\*.txt`, all files anywhere under the :file:`examples` directory -matching :file:`\*.txt` or :file:`\*.py`, and exclude all directories matching -:file:`examples/sample?/build`. All of this is done *after* the standard -include set, so you can exclude files from the standard set with explicit -instructions in the manifest template. (Or, you can use the -:option:`--no-defaults` option to disable the standard set entirely.) - -The order of commands in the manifest template matters: initially, we have the -list of default files as described above, and each command in the template adds -to or removes from that list of files. Once we have fully processed the -manifest template, we remove files that should not be included in the source -distribution: - -* all files in the Packaging "build" tree (default :file:`build/`) - -* all files in directories named :file:`RCS`, :file:`CVS`, :file:`.svn`, - :file:`.hg`, :file:`.git`, :file:`.bzr` or :file:`_darcs` - -Now we have our complete list of files, which is written to the manifest for -future reference, and then used to build the source distribution archive(s). - -You can disable the default set of included files with the -:option:`--no-defaults` option, and you can disable the standard exclude set -with :option:`--no-prune`. - -Following the Packaging' own manifest template, let's trace how the -:command:`sdist` command builds the list of files to include in the Packaging -source distribution: - -#. include all Python source files in the :file:`packaging` and - :file:`packaging/command` subdirectories (because packages corresponding to - those two directories were mentioned in the :option:`packages` option in the - setup script---see section :ref:`packaging-setup-script`) - -#. include :file:`README.txt`, :file:`setup.py`, and :file:`setup.cfg` (standard - files) - -#. include :file:`test/test\*.py` (standard files) - -#. include :file:`\*.txt` in the distribution root (this will find - :file:`README.txt` a second time, but such redundancies are weeded out later) - -#. include anything matching :file:`\*.txt` or :file:`\*.py` in the sub-tree - under :file:`examples`, - -#. exclude all files in the sub-trees starting at directories matching - :file:`examples/sample?/build`\ ---this may exclude files included by the - previous two steps, so it's important that the ``prune`` command in the manifest - template comes after the ``recursive-include`` command - -#. exclude the entire :file:`build` tree, and any :file:`RCS`, :file:`CVS`, - :file:`.svn`, :file:`.hg`, :file:`.git`, :file:`.bzr` and :file:`_darcs` - directories - -Just like in the setup script, file and directory names in the manifest template -should always be slash-separated; the Packaging will take care of converting -them to the standard representation on your platform. That way, the manifest -template is portable across operating systems. - -Commands --------- - -The manifest template commands are: - -+-------------------------------------------+-----------------------------------------------+ -| Command | Description | -+===========================================+===============================================+ -| :command:`include pat1 pat2 ...` | include all files matching any of the listed | -| | patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`exclude pat1 pat2 ...` | exclude all files matching any of the listed | -| | patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`recursive-include dir pat1 pat2 | include all files under *dir* matching any of | -| ...` | the listed patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`recursive-exclude dir pat1 pat2 | exclude all files under *dir* matching any of | -| ...` | the listed patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`global-include pat1 pat2 ...` | include all files anywhere in the source tree | -| | matching --- & any of the listed patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`global-exclude pat1 pat2 ...` | exclude all files anywhere in the source tree | -| | matching --- & any of the listed patterns | -+-------------------------------------------+-----------------------------------------------+ -| :command:`prune dir` | exclude all files under *dir* | -+-------------------------------------------+-----------------------------------------------+ -| :command:`graft dir` | include all files under *dir* | -+-------------------------------------------+-----------------------------------------------+ - -The patterns here are Unix-style "glob" patterns: ``*`` matches any sequence of -regular filename characters, ``?`` matches any single regular filename -character, and ``[range]`` matches any of the characters in *range* (e.g., -``a-z``, ``a-zA-Z``, ``a-f0-9_.``). The definition of "regular filename -character" is platform-specific: on Unix it is anything except slash; on Windows -anything except backslash or colon. diff --git a/Doc/packaging/tutorial.rst b/Doc/packaging/tutorial.rst deleted file mode 100644 index 04f41e519de9..000000000000 --- a/Doc/packaging/tutorial.rst +++ /dev/null @@ -1,112 +0,0 @@ -================== -Packaging tutorial -================== - -Welcome to the Packaging tutorial! We will learn how to use Packaging -to package your project. - -.. TODO merge with introduction.rst - - -Getting started ---------------- - -Packaging works with the *setup.cfg* file. It contains all the metadata for -your project, as defined in PEP 345, but also declare what your project -contains. - -Let's say you have a project called *CLVault* containing one package called -*clvault*, and a few scripts inside. You can use the *pysetup* script to create -a *setup.cfg* file for the project. The script will ask you a few questions:: - - $ mkdir CLVault - $ cd CLVault - $ pysetup create - Project name [CLVault]: - Current version number: 0.1 - Package description: - >Command-line utility to store and retrieve passwords - Author name: Tarek Ziade - Author e-mail address: tarek@ziade.org - Project Home Page: http://bitbucket.org/tarek/clvault - Do you want to add a package ? (y/n): y - Package name: clvault - Do you want to add a package ? (y/n): n - Do you want to set Trove classifiers? (y/n): y - Please select the project status: - - 1 - Planning - 2 - Pre-Alpha - 3 - Alpha - 4 - Beta - 5 - Production/Stable - 6 - Mature - 7 - Inactive - - Status: 3 - What license do you use: GPL - Matching licenses: - - 1) License :: OSI Approved :: GNU General Public License (GPL) - 2) License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) - - Type the number of the license you wish to use or ? to try again:: 1 - Do you want to set other trove identifiers (y/n) [n]: n - Wrote "setup.cfg". - - -A setup.cfg file is created, containing the metadata of your project and the -list of the packages it contains:: - - $ cat setup.cfg - [metadata] - name = CLVault - version = 0.1 - author = Tarek Ziade - author_email = tarek@ziade.org - description = Command-line utility to store and retrieve passwords - home_page = http://bitbucket.org/tarek/clvault - - classifier = Development Status :: 3 - Alpha - License :: OSI Approved :: GNU General Public License (GPL) - - [files] - packages = clvault - - -Our project will depend on the *keyring* project. Let's add it in the -[metadata] section:: - - [metadata] - ... - requires_dist = - keyring - - -Running commands ----------------- - -You can run useful commands on your project once the setup.cfg file is ready: - -- sdist: creates a source distribution -- register: register your project to PyPI -- upload: upload the distribution to PyPI -- install_dist: install it - -All commands are run using the run script:: - - $ pysetup run install_dist - $ pysetup run sdist - $ pysetup run upload - -If you want to push a source distribution of your project to PyPI, do:: - - $ pysetup run sdist register upload - - -Installing the project ----------------------- - -The project can be installed by manually running the packaging install command:: - - $ pysetup run install_dist diff --git a/Doc/packaging/uploading.rst b/Doc/packaging/uploading.rst deleted file mode 100644 index 297518bb8370..000000000000 --- a/Doc/packaging/uploading.rst +++ /dev/null @@ -1,80 +0,0 @@ -.. _packaging-package-upload: - -*************************************** -Uploading Packages to the Package Index -*************************************** - -The Python Package Index (PyPI) not only stores the package info, but also the -package data if the author of the package wishes to. The packaging command -:command:`upload` pushes the distribution files to PyPI. - -The command is invoked immediately after building one or more distribution -files. For example, the command :: - - python setup.py sdist bdist_wininst upload - -will cause the source distribution and the Windows installer to be uploaded to -PyPI. Note that these will be uploaded even if they are built using an earlier -invocation of :file:`setup.py`, but that only distributions named on the command -line for the invocation including the :command:`upload` command are uploaded. - -The :command:`upload` command uses the username, password, and repository URL -from the :file:`$HOME/.pypirc` file (see section :ref:`packaging-pypirc` for more on this -file). If a :command:`register` command was previously called in the same -command, and if the password was entered in the prompt, :command:`upload` will -reuse the entered password. This is useful if you do not want to store a clear -text password in the :file:`$HOME/.pypirc` file. - -You can specify another PyPI server with the :option:`--repository=*url*` -option:: - - python setup.py sdist bdist_wininst upload -r http://example.com/pypi - -See section :ref:`packaging-pypirc` for more on defining several servers. - -You can use the :option:`--sign` option to tell :command:`upload` to sign each -uploaded file using GPG (GNU Privacy Guard). The :program:`gpg` program must -be available for execution on the system :envvar:`PATH`. You can also specify -which key to use for signing using the :option:`--identity=*name*` option. - -Other :command:`upload` options include :option:`--repository=` or -:option:`--repository=
` where *url* is the url of the server and -*section* the name of the section in :file:`$HOME/.pypirc`, and -:option:`--show-response` (which displays the full response text from the PyPI -server for help in debugging upload problems). - -PyPI package display -==================== - -The ``description`` field plays a special role at PyPI. It is used by -the server to display a home page for the registered package. - -If you use the `reStructuredText `_ -syntax for this field, PyPI will parse it and display an HTML output for -the package home page. - -The ``description`` field can be filled from a text file located in the -project:: - - from packaging.core import setup - - fp = open('README.txt') - try: - description = fp.read() - finally: - fp.close() - - setup(name='Packaging', - description=description) - -In that case, :file:`README.txt` is a regular reStructuredText text file located -in the root of the package besides :file:`setup.py`. - -To prevent registering broken reStructuredText content, you can use the -:program:`rst2html` program that is provided by the :mod:`docutils` package -and check the ``description`` from the command line:: - - $ python setup.py --description | rst2html.py > output.html - -:mod:`docutils` will display a warning if there's something wrong with your -syntax. diff --git a/Doc/tools/sphinxext/indexcontent.html b/Doc/tools/sphinxext/indexcontent.html index abe17f3d231e..7f8547020f1b 100644 --- a/Doc/tools/sphinxext/indexcontent.html +++ b/Doc/tools/sphinxext/indexcontent.html @@ -20,10 +20,10 @@ tutorial for C/C++ programmers

- - + + diff --git a/Doc/tools/sphinxext/susp-ignored.csv b/Doc/tools/sphinxext/susp-ignored.csv index e813f93d2265..05b7c65d4d7c 100644 --- a/Doc/tools/sphinxext/susp-ignored.csv +++ b/Doc/tools/sphinxext/susp-ignored.csv @@ -243,28 +243,6 @@ license,,`,THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AN license,,`,* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY license,,`,THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND license,,:zooko,mailto:zooko@zooko.com -packaging/examples,,`,This is the description of the ``foobar`` project. -packaging/setupcfg,,::,Development Status :: 3 - Alpha -packaging/setupcfg,,::,License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1) -packaging/setupscript,,::,"'Development Status :: 4 - Beta'," -packaging/setupscript,,::,"'Environment :: Console'," -packaging/setupscript,,::,"'Environment :: Web Environment'," -packaging/setupscript,,::,"'Intended Audience :: Developers'," -packaging/setupscript,,::,"'Intended Audience :: End Users/Desktop'," -packaging/setupscript,,::,"'Intended Audience :: System Administrators'," -packaging/setupscript,,::,"'License :: OSI Approved :: Python Software Foundation License'," -packaging/setupscript,,::,"'Operating System :: MacOS :: MacOS X'," -packaging/setupscript,,::,"'Operating System :: Microsoft :: Windows'," -packaging/setupscript,,::,"'Operating System :: POSIX'," -packaging/setupscript,,::,"'Programming Language :: Python'," -packaging/setupscript,,::,"'Topic :: Communications :: Email'," -packaging/setupscript,,::,"'Topic :: Office/Business'," -packaging/setupscript,,::,"'Topic :: Software Development :: Bug Tracking'," -packaging/tutorial,,::,1) License :: OSI Approved :: GNU General Public License (GPL) -packaging/tutorial,,::,2) License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) -packaging/tutorial,,::,classifier = Development Status :: 3 - Alpha -packaging/tutorial,,::,License :: OSI Approved :: GNU General Public License (GPL) -packaging/tutorial,,::,Type the number of the license you wish to use or ? to try again:: 1 reference/datamodel,,:max, reference/datamodel,,:step,a[i:j:step] reference/expressions,,:datum,{key:datum...} diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 40e850e9eddf..b14f3704dcf5 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -528,8 +528,8 @@ These environment variables influence Python's behavior. Defines the :data:`user base directory `, which is used to compute the path of the :data:`user site-packages directory ` - and :ref:`Packaging installation paths ` for - ``pysetup run install_dist --user``. + and :ref:`Distutils installation paths ` for + ``python setup.py install --user``. .. seealso:: diff --git a/Doc/using/scripts.rst b/Doc/using/scripts.rst index 88a9de62c820..2d28246f8d48 100644 --- a/Doc/using/scripts.rst +++ b/Doc/using/scripts.rst @@ -16,8 +16,7 @@ directories that don't exist already) and places a ``pyvenv.cfg`` file in it with a ``home`` key pointing to the Python installation the command was run from. It also creates a ``bin`` (or ``Scripts`` on Windows) subdirectory containing a copy of the ``python`` binary (or -binaries, in the case of Windows) and the ``pysetup3`` script (to -facilitate easy installation of packages from PyPI into the new virtualenv). +binaries, in the case of Windows). It also creates an (initially empty) ``lib/pythonX.Y/site-packages`` subdirectory (on Windows, this is ``Lib\site-packages``). diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index c6225c39c1c2..25a0ece76832 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -53,23 +53,28 @@ This article explains the new features in Python 3.3, compared to 3.2. release, so it's worth checking back even after reading earlier versions. -New packaging infrastructure -============================ +PEP 405: Virtual Environments +============================= -The standard library's packaging infrastructure has been updated to adopt -some of the features developed by the wider community. +- inspired by ``virtualenv``, a tool widely used by the community +- change to the interpreter to avoid hacks -* the :mod:`packaging` package and ``pysetup`` script (inspired by - ``setuptools``, ``distribute``, ``distutil2`` and ``pip``) -* the :mod:`venv` module and ``pyvenv`` script (inspired by ``virtualenv``) - (Note: at time of writing, :pep:`405` is accepted, but not yet implemented) -* native support for package directories that don't require ``__init__.py`` - marker files and can automatically span multiple path segments - (inspired by various third party approaches to namespace packages, as - described in :pep:`420`) +The :mod:`venv` module and ``pyvenv`` script (inspired by ``virtualenv``, a +tool widely used by the community). +.. also mention the interpreter changes that avoid the hacks used in virtualenv -.. pep-3118-update: + +PEP 420: Namespace Packages +=========================== + +Native support for package directories that don't require ``__init__.py`` +marker files and can automatically span multiple path segments (inspired by +various third party approaches to namespace packages, as described in +:pep:`420`) + + +.. _pep-3118-update: PEP 3118: New memoryview implementation and buffer protocol documentation ========================================================================= @@ -1219,20 +1224,6 @@ os * :func:`~os.getgrouplist` (:issue:`9344`) -packaging ---------- - -:mod:`distutils` has undergone additions and refactoring under a new name, -:mod:`packaging`, to allow developers to make far-reaching changes without -being constrained by backward compatibility. -:mod:`distutils` is still provided in the standard library, but users are -encouraged to transition to :mod:`packaging`. For older versions of Python, a -backport compatible with Python 2.5 and newer and 3.2 is available on PyPI -under the name `distutils2 `_. - -.. TODO add examples and howto to the packaging docs and link to them - - pdb --- @@ -1560,8 +1551,6 @@ are no longer supported due to maintenance burden. Deprecated Python modules, functions and methods ------------------------------------------------ -* The :mod:`distutils` module has been deprecated. Use the new - :mod:`packaging` module instead. * The ``unicode_internal`` codec has been deprecated because of the :pep:`393`, use UTF-8, UTF-16 (``utf-16-le`` or ``utf-16-be``), or UTF-32 (``utf-32-le`` or ``utf-32-be``) diff --git a/Lib/packaging/__init__.py b/Lib/packaging/__init__.py deleted file mode 100644 index 93b611765cc5..000000000000 --- a/Lib/packaging/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -"""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 deleted file mode 100644 index f527bc49ed10..000000000000 --- a/Lib/packaging/_trove.py +++ /dev/null @@ -1,571 +0,0 @@ -"""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 :: Buildout :: Extension', -'Framework :: Buildout :: Recipe', -'Framework :: Chandler', -'Framework :: CherryPy', -'Framework :: CubicWeb', -'Framework :: Django', -'Framework :: IDLE', -'Framework :: Paste', -'Framework :: Plone', -'Framework :: Plone :: 3.2', -'Framework :: Plone :: 3.3', -'Framework :: Plone :: 4.0', -'Framework :: Plone :: 4.1', -'Framework :: Plone :: 4.2', -'Framework :: Plone :: 4.3', -'Framework :: Pylons', -'Framework :: Setuptools Plugin', -'Framework :: Trac', -'Framework :: Tryton', -'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 :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', -'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 :: Python :: Implementation', -'Programming Language :: Python :: Implementation :: CPython', -'Programming Language :: Python :: Implementation :: IronPython', -'Programming Language :: Python :: Implementation :: Jython', -'Programming Language :: Python :: Implementation :: PyPy', -'Programming Language :: Python :: Implementation :: Stackless', -'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 :: Session', -'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 Life', -'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 deleted file mode 100644 index 87227c0b69dc..000000000000 --- a/Lib/packaging/command/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Subpackage containing all standard commands.""" -import os -from packaging.errors import PackagingModuleError -from packaging.util import resolve_name - -__all__ = ['get_command_names', 'set_command', 'get_command_class', - 'STANDARD_COMMANDS'] - - -STANDARD_COMMANDS = [ - # packaging - 'check', 'test', - # building - 'build', 'build_py', 'build_ext', 'build_clib', 'build_scripts', 'clean', - # installing - 'install_dist', 'install_lib', 'install_headers', 'install_scripts', - 'install_data', 'install_distinfo', - # distributing - 'sdist', 'bdist', 'bdist_dumb', 'bdist_wininst', - 'register', 'upload', 'upload_docs', - ] - -if os.name == 'nt': - STANDARD_COMMANDS.insert(STANDARD_COMMANDS.index('bdist_wininst'), - 'bdist_msi') - -# XXX maybe we need more than one registry, so that --list-comands can display -# standard, custom and overriden standard commands differently -_COMMANDS = dict((name, 'packaging.command.%s.%s' % (name, name)) - for name in STANDARD_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] - except KeyError: - raise PackagingModuleError("Invalid command %s" % name) - if isinstance(cls, str): - cls = resolve_name(cls) - _COMMANDS[name] = cls - return cls diff --git a/Lib/packaging/command/bdist.py b/Lib/packaging/command/bdist.py deleted file mode 100644 index e390cdc369d4..000000000000 --- a/Lib/packaging/command/bdist.py +++ /dev/null @@ -1,141 +0,0 @@ -"""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', 'tar', - 'wininst', 'zip', 'msi'] - - # And the real information. - format_command = {'gztar': ('bdist_dumb', "gzip'ed tar file"), - 'bztar': ('bdist_dumb', "bzip2'ed 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.reinitialize_command(cmd_name) - sub_cmd.format = self.formats[i] - - # 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 deleted file mode 100644 index 548e3c4930c8..000000000000 --- a/Lib/packaging/command/bdist_dumb.py +++ /dev/null @@ -1,139 +0,0 @@ -"""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, gztar, bztar, 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 = None - 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', 'skip_build') - - def run(self): - if not self.skip_build: - self.run_command('build') - - install = self.reinitialize_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 deleted file mode 100644 index 995eec57e5ae..000000000000 --- a/Lib/packaging/command/bdist_msi.py +++ /dev/null @@ -1,743 +0,0 @@ -"""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 shutil import rmtree -from sysconfig import get_python_version -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)""" - super(PyDialog, self).__init__(*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 = None - self.install_script = None - self.pre_install_script = None - self.versions = None - - def finalize_options(self): - self.set_undefined_options('bdist', 'skip_build') - - 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.reinitialize_command('install_dist', - reinit_subcommands=True) - install.prefix = self.bdist_dir - install.skip_build = self.skip_build - install.warn_dir = False - - install_lib = self.reinitialize_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 = '%s.%s' % sys.version_info[:2] - 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 deleted file mode 100644 index 3c66360ecdca..000000000000 --- a/Lib/packaging/command/bdist_wininst.py +++ /dev/null @@ -1,345 +0,0 @@ -"""Create an executable installer for Windows.""" - -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 = None - self.install_script = None - self.pre_install_script = None - self.user_access_control = None - - - def finalize_options(self): - self.set_undefined_options('bdist', 'skip_build') - - 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.reinitialize_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.reinitialize_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 = '%s.%s' % sys.version_info[:2] - 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: - logger.info('removing %s', self.bdist_dir) - if not self.dry_run: - 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 + b"\0" - if self.pre_install_script: - # We need to normalize newlines, so we open in text mode and - # convert back to bytes. "latin-1" simply avoids any possible - # failures. - with open(self.pre_install_script, encoding="latin-1") as fp: - script_data = fp.read().encode("latin-1") - cfgdata = cfgdata + script_data + b"\n\0" - else: - # empty pre-install script - cfgdata = cfgdata + b"\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 deleted file mode 100644 index fcb50df4e49e..000000000000 --- a/Lib/packaging/command/build.py +++ /dev/null @@ -1,151 +0,0 @@ -"""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 separate 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)") - pyversion = '%s.%s' % sys.version_info[:2] - plat_specifier = ".%s-%s" % (self.plat_name, pyversion) - - # 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-' + pyversion) - - 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 deleted file mode 100644 index 5388ccd4d47a..000000000000 --- a/Lib/packaging/command/build_clib.py +++ /dev/null @@ -1,197 +0,0 @@ -"""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, new_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! - 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 deleted file mode 100644 index 7aa0b3a74155..000000000000 --- a/Lib/packaging/command/build_ext.py +++ /dev/null @@ -1,644 +0,0 @@ -"""Build extension modules.""" - -import os -import re -import sys -import site -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 - -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 turn them into 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"), - ('user', None, - "add user include, library and rpath"), - ('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', '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 - 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) - - self.ensure_string_list('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. - # Note that we must use sys.base_exec_prefix here rather than - # exec_prefix, since the Python libs are not copied to a virtual - # environment. - self.library_dirs.append(os.path.join(sys.base_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', 'gnu', '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 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 - - 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) - - # Setup the CCompiler object that we'll use to do all the - # compiling and linking - self.compiler_obj = new_compiler(compiler=self.compiler, - 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 = "PyInit_" + 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.version_info[:2] - # 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.version_info[:2] - # 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.version_info[:2] - # 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.version_info[:2] - # 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' + sys.abiflags - pythonlib = template % sys.version_info[:2] - return ext.libraries + [pythonlib] - else: - return ext.libraries diff --git a/Lib/packaging/command/build_py.py b/Lib/packaging/command/build_py.py deleted file mode 100644 index 00621400da31..000000000000 --- a/Lib/packaging/command/build_py.py +++ /dev/null @@ -1,392 +0,0 @@ -"""Build pure Python modules (just copy to build directory).""" - -import os -import imp -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)" - - # The options for controlling byte compilation are two independent sets; - # more info in install_lib or the reST docs - - 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 separate 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), - prefix=self.build_lib) - - # -- 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) - doctests = self.distribution.convert_2to3_doctests - if copied and srcfile in 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 %r 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 %r (for module %r) 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 %r", 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(imp.cache_from_source(filename, True)) - if self.optimize: - outputs.append(imp.cache_from_source(filename, False)) - - 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) diff --git a/Lib/packaging/command/build_scripts.py b/Lib/packaging/command/build_scripts.py deleted file mode 100644 index d651ae01c6fb..000000000000 --- a/Lib/packaging/command/build_scripts.py +++ /dev/null @@ -1,154 +0,0 @@ -"""Build scripts (copy to build dir and fix up shebang line).""" - -import os -import re -import sysconfig -from tokenize import detect_encoding - -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(b'^#!.*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, "rb") - except IOError: - if not self.dry_run: - raise - f = None - else: - encoding, lines = detect_encoding(f.readline) - f.seek(0) - 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 b'' - - if adjust: - logger.info("copying and adjusting %s -> %s", script, - self.build_dir) - if not self.dry_run: - if not sysconfig.is_python_build(): - executable = self.executable - else: - executable = os.path.join( - sysconfig.get_config_var("BINDIR"), - "python%s%s" % (sysconfig.get_config_var("VERSION"), - sysconfig.get_config_var("EXE"))) - executable = os.fsencode(executable) - shebang = b"#!" + executable + post_interp + b"\n" - # Python parser starts to read a script using UTF-8 until - # it gets a #coding:xxx cookie. The shebang has to be the - # first line of a file, the #coding:xxx cookie cannot be - # written before. So the shebang has to be decodable from - # UTF-8. - try: - shebang.decode('utf-8') - except UnicodeDecodeError: - raise ValueError( - "The shebang ({!r}) is not decodable " - "from utf-8".format(shebang)) - # If the script is encoded to a custom encoding (use a - # #coding:xxx cookie), the shebang has to be decodable from - # the script encoding too. - try: - shebang.decode(encoding) - except UnicodeDecodeError: - raise ValueError( - "The shebang ({!r}) is not decodable " - "from the script encoding ({})" - .format(shebang, encoding)) - with open(outfile, "wb") as outf: - outf.write(shebang) - outf.writelines(f.readlines()) - 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 deleted file mode 100644 index 6715db90b32f..000000000000 --- a/Lib/packaging/command/check.py +++ /dev/null @@ -1,88 +0,0 @@ -"""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('%s: %s' % (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 deleted file mode 100644 index 4f60f4ea9359..000000000000 --- a/Lib/packaging/command/clean.py +++ /dev/null @@ -1,76 +0,0 @@ -"""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 deleted file mode 100644 index 25e6a72f2cd2..000000000000 --- a/Lib/packaging/command/cmd.py +++ /dev/null @@ -1,461 +0,0 @@ -"""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 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 an instance of Distribution, not %r" - % type(dist)) - 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 reinitialize_command(self, command, reinit_subcommands=False): - return self.distribution.reinitialize_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): - 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 dry-run and force flags. - - (dry-run defaults to whatever is in the Distribution object, and - force 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 dry-run - and force flags. - """ - if self.dry_run: - # XXX should not return but let copy_tree log and decide to execute - # or not based on its dry_run argument - return - - 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 same thing - 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) - - def byte_compile(self, files, prefix=None): - """Byte-compile files to pyc and/or pyo files. - - This method requires that the calling class define compile and - optimize options, like build_py and install_lib. It also - automatically respects the force and dry-run options. - - prefix, if given, is a string that will be stripped off the - filenames encoded in bytecode files. - """ - if self.compile: - util.byte_compile(files, optimize=False, prefix=prefix, - force=self.force, dry_run=self.dry_run) - if self.optimize: - util.byte_compile(files, optimize=self.optimize, prefix=prefix, - force=self.force, dry_run=self.dry_run) diff --git a/Lib/packaging/command/command_template b/Lib/packaging/command/command_template deleted file mode 100644 index a12d32bfb33c..000000000000 --- a/Lib/packaging/command/command_template +++ /dev/null @@ -1,35 +0,0 @@ -"""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 deleted file mode 100644 index 264c1396b232..000000000000 --- a/Lib/packaging/command/config.py +++ /dev/null @@ -1,349 +0,0 @@ -"""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] - with open(filename, "w") as file: - 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") - 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) - - with open(out) as file: - match = False - while True: - line = file.readline() - if line == '': - break - if pattern.search(line): - match = True - break - - 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 deleted file mode 100644 index 9ca6279533e8..000000000000 --- a/Lib/packaging/command/install_data.py +++ /dev/null @@ -1,79 +0,0 @@ -"""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 deleted file mode 100644 index 8388dc9fe0bd..000000000000 --- a/Lib/packaging/command/install_dist.py +++ /dev/null @@ -1,605 +0,0 @@ -"""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 - - -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"), - ('user', None, - "install in user site-packages directory [%s]" % - get_path('purelib', '%s_user' % os.name)), - ('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 for details - ('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', '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 - 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 - 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 - - # -- 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 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 = '%s.%s' % sys.version_info[:2] - 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, - 'userbase': self.install_userbase, - '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 under USERBASE - if self.user: - self.create_user_dirs() - - # 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', '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 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 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_user_dirs(self): - """Create directories under USERBASE as needed.""" - 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 deleted file mode 100644 index b49729f5afa7..000000000000 --- a/Lib/packaging/command/install_distinfo.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Create the PEP 376-compliant .dist-info directory.""" - -# Forked from the former install_egg_info command by Josip Djolonga - -import os -import csv -import hashlib -from shutil import rmtree - -from packaging import logger -from packaging.command.cmd import Command - - -class install_distinfo(Command): - - description = 'create a .dist-info directory for the distribution' - - user_options = [ - ('install-dir=', None, - "directory where the the .dist-info directory will be created"), - ('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 RESOURCES file"), - ] - - boolean_options = ['requested', 'no-record', 'no-resources'] - - negative_opt = {'no-requested': 'requested'} - - def initialize_options(self): - self.install_dir = None - self.installer = None - self.requested = None - self.no_record = None - self.no_resources = None - self.outfiles = [] - - def finalize_options(self): - self.set_undefined_options('install_dist', - 'installer', 'requested', 'no_record') - - self.set_undefined_options('install_lib', 'install_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 = metadata.get_fullname(filesafe=True) + ".dist-info" - - self.install_dir = os.path.join(self.install_dir, basename) - - def run(self): - target = self.install_dir - - if os.path.isdir(target) and not os.path.islink(target): - if not self.dry_run: - rmtree(target) - elif os.path.exists(target): - self.execute(os.unlink, (self.install_dir,), - "removing " + target) - - self.execute(os.makedirs, (target,), "creating " + target) - - metadata_path = os.path.join(self.install_dir, 'METADATA') - self.execute(self.distribution.metadata.write, (metadata_path,), - "creating " + metadata_path) - self.outfiles.append(metadata_path) - - installer_path = os.path.join(self.install_dir, 'INSTALLER') - logger.info('creating %s', installer_path) - if not self.dry_run: - with open(installer_path, 'w') as f: - f.write(self.installer) - self.outfiles.append(installer_path) - - if self.requested: - requested_path = os.path.join(self.install_dir, 'REQUESTED') - logger.info('creating %s', requested_path) - if not self.dry_run: - open(requested_path, 'wb').close() - self.outfiles.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.install_dir, - 'RESOURCES') - logger.info('creating %s', resources_path) - if not self.dry_run: - with open(resources_path, 'w') as f: - writer = csv.writer(f, delimiter=',', - lineterminator='\n', - quotechar='"') - for row in install_data.get_resources_out(): - writer.writerow(row) - - self.outfiles.append(resources_path) - - if not self.no_record: - record_path = os.path.join(self.install_dir, 'RECORD') - logger.info('creating %s', record_path) - if not self.dry_run: - with open(record_path, 'w', encoding='utf-8') as f: - writer = csv.writer(f, delimiter=',', - lineterminator='\n', - 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, 'rb') as fp: - hash = hashlib.md5() - hash.update(fp.read()) - md5sum = hash.hexdigest() - writer.writerow((fpath, md5sum, size)) - - # add the RECORD file itself - writer.writerow((record_path, '', '')) - self.outfiles.append(record_path) - - def get_outputs(self): - return self.outfiles diff --git a/Lib/packaging/command/install_headers.py b/Lib/packaging/command/install_headers.py deleted file mode 100644 index e043d6b8ed2a..000000000000 --- a/Lib/packaging/command/install_headers.py +++ /dev/null @@ -1,43 +0,0 @@ -"""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 deleted file mode 100644 index ffc5d457e7ff..000000000000 --- a/Lib/packaging/command/install_lib.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Install all modules (extensions and pure Python).""" - -import os -import imp - -from packaging import logger -from packaging.command.cmd import Command -from packaging.errors import PackagingOptionError - - -# Extension for Python source files. -# XXX dead code? most of the codebase checks for literal '.py' -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 options for controlling byte compilation are two independent sets: - # '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 and/or .pyo - if outfiles is not None and self.distribution.has_pure_modules(): - # XXX comment from distutils: "This [prefix stripping] is far from - # complete, but it should at least generate usable bytecode in RPM - # distributions." -> need to find exact requirements for - # byte-compiled files and fix it - install_root = self.get_finalized_command('install_dist').root - self.byte_compile(outfiles, prefix=install_root) - - # -- 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 - - # -- 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(imp.cache_from_source(py_file, True)) - if self.optimize: - bytecode_files.append(imp.cache_from_source(py_file, False)) - - 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 deleted file mode 100644 index cfacbe25fb8f..000000000000 --- a/Lib/packaging/command/install_scripts.py +++ /dev/null @@ -1,59 +0,0 @@ -"""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 deleted file mode 100644 index 59805f7d47d8..000000000000 --- a/Lib/packaging/command/register.py +++ /dev/null @@ -1,263 +0,0 @@ -"""Register a release with a project index.""" - -# Contributed by Richard Jones - -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, encode_multipart) -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 ('y', 'n'): - 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 - content_type, body = encode_multipart(data.items(), []) - - # build the Request - headers = { - 'Content-type': content_type, - '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 deleted file mode 100644 index d39998119f02..000000000000 --- a/Lib/packaging/command/sdist.py +++ /dev/null @@ -1,347 +0,0 @@ -"""Create a source distribution.""" - -import os -import re -import sys -from io import StringIO -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 default files to self.filelist. - - In addition to the setup.cfg file, this will include all files returned - by the get_source_files of every registered command. This will find - Python modules and packages, data files listed in package_data_, - data_files and extra_files, scripts, C sources of extension modules or - C libraries (headers are missing). - """ - if os.path.exists('setup.cfg'): - self.filelist.append('setup.cfg') - else: - logger.warning("%s: standard 'setup.cfg' file not found", - self.get_command_name()) - - 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, 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, dry_run=dry_run) diff --git a/Lib/packaging/command/test.py b/Lib/packaging/command/test.py deleted file mode 100644 index 4d5348f0c181..000000000000 --- a/Lib/packaging/command/test.py +++ /dev/null @@ -1,80 +0,0 @@ -"""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.reinitialize_command('build') - self.run_command('build') - sys.path.insert(0, build.build_lib) - - # XXX maybe we could pass the verbose argument of pysetup here - 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 deleted file mode 100644 index f56d2c69fedb..000000000000 --- a/Lib/packaging/command/upload.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Upload a distribution to a project index.""" - -import os -import socket -import logging -import platform -import urllib.parse -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, encode_multipart) -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 - files = [] - for key in ('content', 'gpg_signature'): - if key in data: - filename_, value = data.pop(key) - files.append((key, filename_, value)) - - content_type, body = encode_multipart(data.items(), files) - - logger.info("Submitting %s to %s", filename, self.repository) - - # build the Request - headers = {'Content-type': content_type, - 'Content-length': str(len(body)), - 'Authorization': auth} - - request = Request(self.repository, body, 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 deleted file mode 100644 index 30e37b52c914..000000000000 --- a/Lib/packaging/command/upload_docs.py +++ /dev/null @@ -1,131 +0,0 @@ -"""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, - encode_multipart) -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() - with zipfile.ZipFile(destination, "w") as zip_file: - 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) - return destination - - -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 - # FIXME should use explicit encoding - 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 deleted file mode 100644 index 11f98cd2adf1075b7ff7be7f02ebc8e743bb6b9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 222208 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

puJW;Z>wC?j_riSDZ%2DU~vX#q-nc* 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 S{tACZRn>yU^H#1j;{O6W#h3B` diff --git a/Lib/packaging/command/wininst-10.0.exe b/Lib/packaging/command/wininst-10.0.exe deleted file mode 100644 index 8ac6e19b8eeaf7642387123c749f416251c496ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 190464 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%0DnD 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�Cv5NA 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 diff --git a/Lib/packaging/command/wininst-6.0.exe b/Lib/packaging/command/wininst-6.0.exe deleted file mode 100644 index f57c855a613e2de2b00fff1df431b8d08d171077..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 61440 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

{MZ3UJ@%Cxk`nZoeb-!Ep z1z+~)*KJDo@9YP4{cP&PE=9M$b^GJstj^_6eA}BhKJV~#pS{j&A~J+769Zg(dB1Xb z_k-Myk@w&2bSE^@uhQ#7{|`PoB{|Yy|4zW~AtUUwR}3HjPC-DD+$wNFr^I1%muz}_ zSn+jV&Mvp1*(YKL@4vF%`@!r>Lw@2o`>g(Dws^;fyL!JG{HX7#Ndx*sL{96qxbK#p zJwN$XKQtvso%VI!#(QV=M5;Hzu)WA@;?TC|K;0*3XGqoJ^yXO zyxno#Gd}UTneN_u?E=qnQS;Luchbl{yP{oq)39{hq3~rL`HoB8nsz>OyC!SV+U_G4 zp5DTH_vPu*#d`+krMw@qbk408esil=zer74ad7se+68K>@q<;qeotrdm;EqpgzKE? z$=`ZTjVi33a=G&Bq@Rz^m~oWbGub%n&P>sn%CJsbT}O}Yp$K23T{EU>`mJ$i?A*tG zba-OKS3hlx-1uop=nr)^qn?eG$_mBrg@g?GF(_k#K-&M;(IdO9qJ!nC$I&ybd}4;@ zX=ClK9#*a@Xiyyc=AH5PTr%ZXmgYy9{O?V?tq@IG@h~}V@B7;)3|&)`!2Ppd{6yK@ z#5se$pPbdz_wM0&&+q(xe($|&UuFCpzdzvD+1FnEHe_b*FV>Fl-uD~R=aE2k|99mV zU)D~opYrhjy+Ni+IS;E>4*Ry|c>Jsfb-5j0mR-Ei^hL_YuXfqQn7xEuUUYr8>d%oS zUo>iW$2WvkQ%{%aik|cd%&v2e4Sl>W$gX~y=b1lVtT#UY(LC1G;!`jcYR^E6%l74SA&v39;X->|JPjx4pCcO9Sp6*!}uHRf9eRgg3^sl4z@3?h( z-!c8liY*oQc71$QwDTM<`J2Yn?cZiDtl1sjzTXe+znlAg@6zwTb6fs+$DkUYujj{T zw+D?l{8dT6hAmHn-`V=j%gk+`f1LkiMMlZN-#XhI$loD7WccyD!wxro+$Y&9*gs7$ zdT&e;y+_rv^4R1s*P}Mi6uE;ptvPaF<*hv39rxqc6DOW{ICNt^>VMMAyj;56?wj9# zIM_b+<7pobsk`;|&B?QSAMvaF{E^3jlPkyZm&~|xd@kpGPG#3o-wA7*^{ov3ursZsCphyO5S%cSxT+z!r^w5y=r-#5!oKFDEm?Z@}(;`Hx- z{7c}4_g5qxxbx0W6UTjceuRh5f)&F14}M-SVZ3A2L#gUSRKwN=&$^P$ABXk#?m2N> z>=*Z*%^kgLE&q?QB|D^7+zOiJjIG%0>v_1B@ub^^&0CiB@7sCQ%cGg1Aghl)tQS5v zh3yQS;PTmo5j%SwTHS5%rRQ4>>htqm1UpZEch`1Ve)g1Nn~P~%5)XHB|1~amSAD>3 z*9iS@MHlX5EuG~w=*;5TVGD}C6gZwKQ|-;@SH_zfu_gD~9~KVihv$iV zRKFFpFy@mVD(}#z_r&{14#ho8yzooRo~80pS31|4Q{IwHvkQIy`ft@k%HR9ogS5+E zJoMPx7`1w`^8S1844&|g&hEn1;1{lNob&ZN2e;pq9xq(cWId?g=uTtC{_(@p z`8|hhaQ%!pbv zsBvibwL#bS_EDU;`e{vdY2*G?TV?jsoa#E;&WhNYAN5(ljbVMWeLwkVgZ$ONfu9_S z?$dP3^hNu539A-#ygT&Vz+t<-9lg?7_Uj0)Xu0m#{55xe?(i&r(#yE&(Hn=yJbugO zy_Art!!LhxjxIgF>+Pj}LCdZt^eIl=Z5{U<}C)1ky~Qs0Rz!D^~2-P>za%q9}-p zy<!4fd#zbBt4+z?n|HIc^;bX7%bw+$I}s&*0deXTcT(1X{3aC!CPoF>CR-Db3RS)=c*F4=M8 zjQG~ws&6jBmoY;nrQi2O&3C^)Ij`7N+e$H{Mfbn|Y~{^)WH9}rf6FPO`Y~947PdAr zy4Z#}H#j=ij47RS@TlOn{x$BiEtAT=8ul-}n0BBzt@rto-G@RCj_zcA$Y9oySz75XEKI!2 zJ-E0)klfWwdNVyR`l;QOHu0U*U5ZkXy+)`jhfRE1$S6|V(jdv zc~d)1%N~%`=CG;#iknL}{C%g;VEV(#Bds4i?lt$;&D-y9KXs0Nbnr{h`^l17g(g?4 zkD5=E9*8gRe8eMuzFye#+O{z=fzHTF?X+5lFC#YBeu>Nya${?6nTW2trMG&TP-1ZN zt*>@+(1;cXi*`ksbBiaNeD)H0_>7UnKghZfGq&_@*h8nstp&sHjeM6`ki{;`-+b01 z=lTqr+}fANkALWP=~&T1_Q91leGhHVI96=e@@k3B%Am5~{(VZLP8<|WwLZfgn->vn z%CM9USaqm%;qcRKmTnDe`PkHoQMm;Be-~3*)?mBH*#oTDr}?6@+JD4XxlIV$HP?@| z=KO`yg?cxN^1W&*D;EA$ReE`Eu3wwntR$b!$yQ6}rFFYLwy1poyR?r{LY3?mx01EO zI;&*-@Z9GeyQSrpv`kJ}Gut#RbOXxle88&aL8jg1_0QuYB5FmVZns&EkFSOqZeE?z zciZAE#((X~ac350#&h3SlxI|YEPipiEV=LfvpGaWMAo0K?pt0-W3O1m$$kh&*v4it zEyF5X>*^0|-_2h9*xl~qxd`2yH?p89Rk4IkzU9Zy+iZ&tFX)!4TCSTuw9}S?Z-yyL zj+teaC63Cev{!sgtc|JgEM%QE{UfBza@4y-zm_i}lU$4ZtmZuk>UM6G@j|U7hWSnz zeJYYq*q0XBr=KsrRm{@Js*YikseN&}&d&^%?*P`5`%(@^{LWLx&4tjOgna@jW?Rh>0CQ&|;U zS+r|ab?KTrS-J%;k91qId!ubx>>SHV%S*9|Z*Ivv_kM{mm6p3(S_{hT%4eL_O^5A2 z&*vl2o00SJZ_~`89X2VcQT?~1n?F07mG`4;%h$UfuQ*(<_%Ur}W^BQ_oUk7`TlB~5 zNwJ?Xuv^~)!*q?`_po#ytZN&u@RXGw3WzQKG}S%Xju(*=f4Qc__h$L?wqLL0UU^cI zvSk07_G1q(>C<{sy6i$qK5IdXJ`wI+?M1q_#*Sw!3=QW*`K3801!cAvpICF!JM!|Z zS@vgpruC_sbkK0uraz6>Jp4NdnSS@P4J(O^Tz4&zeb1}huakdG(u8RxR;wegbh~Y} zWMPM)Yw`no=2xWZq?Z;?O8WTPgDBc3@=TSe*y)2D+S^t1(bvt?>l+l)&XE|fW>?~~ zWd)u)cchvMjxV(A_u+EElUKz{wx0Y^7FqhdvQNi?^5X`(iocsLOm=ik%}J6K_dP1S zZ2ZOfx%}emDr=OL*wf&ZN3mvZvS=sjo`P+(CQOxib zDYqpI_rg$J{oN0BE*HJhUNSwWrJuLSej)PC593mrbE)watv z&oEMWry7q6>)7$9O&^oIrBgdiedc5D`D=J?f-G z;AndFcMcA|75mSSc3O}6#{RwBEOXheewDUA z&8;S0>=WtZ-8;SCH0JYly?gcjXl$8q$YQ{;vY!3FPj*80Mb&Ct|8=&m;Uaq>c!7XXQ!-N^*4*UIKpl34u zdStJ}naUjr&OKI6)lR=O?cAjwq5|s!;w0C3Qu~@RNzL+?vAJ_aG4Zb_M%xB5gdgpP zMed)W#}gYnatG`YOn!MPfXFDh&F3HgOVGQ;v8X4)C7vahnO??QJB?%a^c|b3O7tt^ zefRCAf7v_i$O@mdM?2lmtWWdMdAHxq$+y~dcHdL1tAkyg+J-2cy`EdTELz+>=zX;R z_#up_U{%4G2}kbgv%j2N9O&?8zW?-&PXh{*?u0!ZQ8AIpejF-zGdE<%yzGcqsYAp2 ze`_&G>K4Y?H%MGQ%yj0H&r^;(&A$HjFU8K%=c5jWzxY`i`*&W{;Ac|}T2`8mwSW7J z+4l9e%@f{CuHseaD^H`1>S zx%FHYbF**Ph_jP^8lBm=s^7W$mPzM3J9!laPq=b=^{8*BZq43yvR%dM%Rc*eUs`r{ z%Y}>6&RuNX?)?>4UFFreVwdYDZ*{%KIBJ!;e~r^}@%Q)@1Dvf_zO?AFCS%OTwfvwx zt9$?RVbznFXSWob|FSu0-}R059oB5BN!*&2EA6{3zGa6Ew)tb%f4uR>k_E$T7LViT zE;T5hwCwsK;li1lUN3U~{9u8$*X;S{`s60B=g*%VVt8U^j|0zVJ-mM-W#yHiIRP2G zd7U~t%)KKw$b4FyzMFY$?VkeeYkPJ`zGl5jn7+S%`*HiEd)x2bcg}3r`&pechP)cI zLp45b=MhI%`j`G^wmP_c+&=xspW6!8ZpuBQC_bdq<;7vAU4=(xUtN%M^+B71ZL>#a zd$o-`u!tC+U(!4Jn6W1(k6o>EGkErOD{}pH8LtUXC5jZ!#OF?%oVZd*7*w_NA+Hn_g@+E=8)^; zpf+E0ZF~6aew^|C`mvuY9=~?C&RY<)p;OfBelo_Ccb@qdy$6*&_&U>u=k{WJtYiAd zTgm6Yp0bPXl(?|1{8_~C%i-nzS-wHFPSq7@W?75oD@=x0nrP2<`cg2a-=1>NPn{O`< z_?kWY*#wS5h^@n}W%|iIhe)0q%sr)->yRNn?o_^`<@dR#I(hXUwEvUu*ulNyJ}%tc zZcW;N!UX2p((VaW7ss9S%(A}uL+fM=OR)3vnn~UKEJq|pY+1AR?>_H8_{4T;`Kcsn z@{7nDOF3nSUlr*u4V;?$a(FayFD)+URWSdt?LRiW3Cnul{mNY*vsqjBc*gM)TBq*q zJ0!6)*3)Nqm;0{Yd98o#k!f?=wLM?1Twzpt)pBp)Zu>6n+-L5L>vQj%Ks9-)mXZ8= z*3V0diyMM>yzgDvd5y=9eIqj6+D*C==kr$Q+WQ5(hpy&J_+MBRUk4=yuvYdM$~Mow z|7!i+69>PZd)eA;>*6l&3?sLC3w1|r2q;YHXZK*uigm-Rt$+HP2OV9vxuWd*;)p5j zhnAbfXWCg`Yq!vAPYcKAZu!Zxn14M-4%khUJ#N9lGnd}p4efqcTD~>A)yIxKo{ig`IIF{DVac9&!ef*&eeGHAvh$v=-frME%{1G( zhs(_|p5G*)Rxck^pALvh&A4(Pv!}AnwW)%m+*M^Qb>3@rWRUj&oTP4NaDUsV-kQG8 zL0!iW)^?XTAzS#QZL7a8zYNMDu$qWYuZNjzN612kP?`p8@lL=4*gy?h3w;0n6oix- zcn9#k`GDu70Sm(Sg#)aw2CW=m&w$oqYdy^JPlPID&Cd@sKR@t)?)d>Wa*>Xg2qi+L zkSoGBS0Mv{P)`JSNvNB|@UvhpQZEf0sfZXd*qSdAAur@Fm&ypaGLaok5K0zEntq<3 z0ox_qwbfoYWy?zV+k6-S*{*@yPl? zKg80w1{Tp?Z4qp2iacBkBE&KcDnbqO=PIM2KF%%B*R9U#3bkn2I!i%Tx@2ooo(e|P z5qc`&*GoPhxk>o`T%kk>K92F&i3%kl_8~w_Jiu>*oQWu*gvLk#sSAS1+Z0I~fVoD? zU#ig1~vZ8X|(LAY5J0 z4=icOFKYRKd?>aEh2u7;N+1XfF>sx32BFo+Rj82RTQTeVG@B75=Smd#RzwZr&eVoDfH4jaj+x|>LjAhL=?ZV3brq9O~iJ>9(3Mt+0=L5UX)GF!_K6Jnk$5utUsMBZ(C0$-Kz zxJq=G#KSib7c!_Y2D1YR!xVXBNemJ@jZ0*MAq)X!u&7*%6Q_{S6|e*`S0s}1U`E#B z)PYY?oS?Bu3;_;rD3_s7@VFA7Sf$00LB}O=Xg5PndO1Q77z=_1pCM4f*ia-xpa4@u z!3+URCKBY$5b&^O5b|RP1R_#%985!A?V!aGN+S^xFa+@sW&rCr<}XmbzML^#Tl zm0|`3%VE&&hu$KN0A@Ro#05;Fl!AnpLJxW5?aao|vVtliF5+;+ksO{%&JlAH5Zb|* z0=}R`yI>TQ2m%>`C_H?}Zi)w)b6Dfxp^CjFb9wAe5qRB}| zJ_Mm72Lf*ru{g?RkO$ug9LV0~nnuacn~S{#T= zG6@%A(?MvB(rlU*hqQrtS{zcV&8ayxr{?tkcEaZoIM+Vtgz*0d{GLPQRdoIu@+a`Q zN7vN}p$hmsG9mxjAeoAx{?@p@eiG^}yEq|j^?3-?lQgbxg!*BP z`f{ir>0DoDO9je!SoDVb2zhd;0&3yZa#0aZ?<44qO*r1A!VC@5kyox&^Y4zc(w$+shgGj}LUiIeth?B4sL6yl5sRJL)|H z>gTvf{`9*D1S$#nj1*HMRWiA8T%m{?3EON~-Qx8Hr2u!b4Qg|zz-wT7)$0z}1fQvt zGAXSv)mmZpGgI_qT2aqsF>Mme5)drLgQfCV*zVTtEiL+MBH(cmN-tRA;LSPiQd8Gi z@+jCQOO%Sn7x7MyP>@2RNCjl)1~%#g9z@d@A(2}M*sBwA0hdQuc==$<0*b;lmaMGf*n#N>EU=P{9lqPUXt+bABQblXhAlWP&9m zN>y_5SwGk<$gP|~!7ZFY1~ilpqd?e_B;sc4v?e(qbH(^?~!OuM?m=);Ol#OA3YGh`}KQzeG z&)3t>$8EG3o=^NBs)M|!CvtlsB!_*!T8CH+ztJNd?hIPd{tB@`1Qw?DQ|u=ilxiXi zGd}J!mmEJtFpfVyG(diADwQpxa4N6Oe48sU@el64n%dRpZ867f){6o^%ckHzbCN5k2T;a;S6 zI8;WRvO>Fct0Nk*H+VO7-U=2=BoMusRw^b2nklT}g(R}INE+3fycahT>%IXxvKeZ_ z3?!nMuFg!d>kiC`Byoh9Vl46er?4HrwI9QIGoGLpZ5lUW8EMq|fX?Fj?iPaETM}R*w*>po{y@zWoJgqch zu|Ih*4)GPb4|65N8o4^6{~`$-?;Gfy3JPS*RL2l@9p*1O)@aa*7A<94RD7jYg>ke@ z8uFnv@EZg3QjAv+a+ZX}7YilS8d8OS9EEuPZ2>lQg%ZcN-pnW|2se;?mj)<;4^t`z zW1TqEK_TXmF~B+!NA7z5eDa819e;j-(*jmW8k!8owW5!`>*6r&js(9K0^6V#H(Uu; zBvZX-6%u$^hspT=4o<5&9P*_f_1AfI{fs2)8btp+v8!&ZjiMLyMbo88MPgWkDFdMO zFgkEkc)xBdG~nv32%=z`+ZM7tjcEX}f@d{i+Php_S%JnJIwTyWtkZQ6t`f zD+q*gaijGJT<0~688BH0Nc$%DNY+fSdb~i#*R52fEx>oM9F_}AiAo#^)(nl2NBebl zFQe9BO!XU4utK?F=pv~%at~F9f?Jm3A7m2i=CsH}+Cda3kp8u$QC#E7L4yt9^xTS1 z>81GRXVR8X5?=-JlYU=}Ac}k)3UoxsQ}^8hOothX-!a2c&z3TB7f_$HnvVpVQ*&xg z&8ayxr{>h0np1OXPXAvf2Kl=>6QP+cA-9JdHQEi~^PcEm;MxekLll5B@gY|lPQ7ASo$OyBl&*n)?=|$EQD1-Z z7NIbWy3>1vI%?os;4>q9F9OMhx|<+fzsdV3(2Ex@P+3_Sx_kF7I)3~(N=ZpU&d$#G zycgNp+M@pb`=idCJ0l|_BczAl16@{&cJe6txBs>DKZUNcwwBJIpz&S%3%s@g9n^nP zSuOrwr*+@I!pXXtu9B*&Su{wE;hJ={hfm;o?ct1!6SYt|jK6WYAv^4ztgCr5&lNV{2ai4z9r%2**4jrl>>jdAP1nf}Ax0{MOrBV9JB zZHgbuHiYKq85lzGGlK818mLj_u{8m|jISfXH!?G##%Gd|L5`1)xf-tqI8}xsYKk8z zLz=56U2bQqhU0JB#{8Iw(i|Qo=nV8pmpdA%;rP2_qw#&ira;+97`74k(B;rTH5|U3 zgB!_{GQFQ_oBWCGk}e<4P}?g0Zp04^{ySKDoy*~x`hzp-3v03=zdDbg^IFm&sE^t& z@TZz--iVH}e05HbQDdBS^@jfbR^1YnS5q?8kSkTkV{b@DS$-{*zojfyQ;$?#9bPo} zk(OWklg!EQYPGdeHdJq$P4S~!t>@>V{;s#pM*NIP9qq&(pyow$!dy*u*4btwe(gw` z0A~+V^P|0h z6%Cr==k@(7ExoZ#$g^Eo&A;SF>J67NG8*X5cz^Z37$5EHfh?nT&LK_MOB4J^kEiU= zm`VOBVDzhf{z^xAJZ1A?P=-nq{_yL3OHVeGuj6vYiH3gvv(6V9(A7WZ55So4;lk=sL6!(4M?xfMxNP+Q z1GPe*QAbqK0b||F(Jn-D&Y!MHzTp70A->?$W=hW zL_8>op+73X#GI`G<^uRaxDpFxUw|gSH70zdG`xR1qy#p}bo-p2I^n)DlEX}P5GGris9gtr%+K#leDS6b^Hh$8A0c~uD zHuUf|gv6nC01fq{_us0YHg)=8lae4nB6$2VkT{kBCH;n);gZs%u26DQ!Bqk|G+;1O zZh0np1OX UPR*$~HK*p(oSIW}`oAUpFD=9J00000 diff --git a/Lib/packaging/command/wininst-7.1.exe b/Lib/packaging/command/wininst-7.1.exe deleted file mode 100644 index 1433bc1ad3775ec9277e13ca8bdca3a5dbc23643..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 65536 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 diff --git a/Lib/packaging/command/wininst-8.0.exe b/Lib/packaging/command/wininst-8.0.exe deleted file mode 100644 index 7403bfabf5cc10c13ef2b6a2ea276b4f6d26ff37..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 61440 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 z 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 diff --git a/Lib/packaging/command/wininst-9.0-amd64.exe b/Lib/packaging/command/wininst-9.0-amd64.exe deleted file mode 100644 index 11d8011c717c3a1c54afbe00e002fab6d37766c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 223744 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

*fXaaT~vFh}gL+%0h0vLL`8; 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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 deleted file mode 100644 index 95526676ff08..000000000000 --- a/Lib/packaging/compiler/cygwinccompiler.py +++ /dev/null @@ -1,355 +0,0 @@ -"""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 - -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 - -# TODO use platform instead of sys.version -# (platform does unholy sys.version parsing too, but at least it gives other -# VMs a chance to override the returned values) - - -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, dry_run=False, force=False): - super(CygwinCCompiler, self).__init__(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 = list(extra_preargs or []) - libraries = list(libraries or []) - objects = list(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") - - super(CygwinCCompiler, self).link( - 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, dry_run=False, force=False): - super(Mingw32CCompiler, self).__init__(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 deleted file mode 100644 index 66f6e9a6bb07..000000000000 --- a/Lib/packaging/compiler/extension.py +++ /dev/null @@ -1,121 +0,0 @@ -"""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 deleted file mode 100644 index 82659fe47829..000000000000 --- a/Lib/packaging/compiler/msvc9compiler.py +++ /dev/null @@ -1,721 +0,0 @@ -"""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 pysetup.""") - - 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, dry_run=False, force=False): - super(MSVCCompiler, self).__init__(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'].split(os.pathsep) - os.environ['lib'] = vc_env['lib'] - os.environ['include'] = vc_env['include'] - - 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) - if self.__version < 10: - 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 deleted file mode 100644 index 39a10b277c05..000000000000 --- a/Lib/packaging/compiler/msvccompiler.py +++ /dev/null @@ -1,635 +0,0 @@ -"""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 pysetup.""") - - 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, dry_run=False, force=False): - super(MSVCCompiler, self).__init__(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 deleted file mode 100644 index 3458faabe099..000000000000 --- a/Lib/packaging/compiler/unixccompiler.py +++ /dev/null @@ -1,339 +0,0 @@ -"""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 pysetup 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. XXX 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 deleted file mode 100644 index ab026a83b687..000000000000 --- a/Lib/packaging/config.py +++ /dev/null @@ -1,391 +0,0 @@ -"""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, - split_multiline) -from packaging.compiler import set_compiler -from packaging.command import set_command -from packaging.markers import interpret - - -def _check_name(name, packages): - if '.' not in name: - return - parts = name.split('.') - parent = '.'.join(parts[:-1]) - if parent not in packages: - # we could log a warning instead of raising, but what's the use - # of letting people build modules they can't import? - raise PackagingOptionError( - 'parent package for extension %r not found' % name) - - -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 = [] - # the line separator is \n for setup.cfg files - for field in vals_str.split('\n'): - 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): - # normalizes and returns a lstripped-/-separated path - base = base.replace(os.path.sep, '/') - path = path.replace(os.path.sep, '/') - 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) - rel_dest = dest.replace(os.path.sep, '/').rstrip('/') - destinations[resource_file] = rel_dest + '/' + rel_path - return destinations - - -class Config: - """Class used to work with configuration files""" - def __init__(self, dist): - self.dist = dist - self.setup_hooks = [] - - def run_hooks(self, config): - """Run setup hooks in the order defined in the spec.""" - for hook in self.setup_hooks: - 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 _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 hooks are called first - if 'global' in content: - if 'setup_hooks' in content['global']: - setup_hooks = split_multiline(content['global']['setup_hooks']) - - # add project directory to sys.path, to allow hooks to be - # distributed with the project - sys.path.insert(0, cfg_directory) - try: - for line in setup_hooks: - try: - hook = resolve_name(line) - except ImportError as e: - logger.warning('cannot find setup hook: %s', - e.args[0]) - else: - self.setup_hooks.append(hook) - self.run_hooks(content) - finally: - sys.path.pop(0) - - 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 = split_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) - - filenames = value.split() - - # concatenate all files - value = [] - for filename in filenames: - # will raise if file not found - with open(filename) as description_file: - value.append(description_file.read().strip()) - # add filename as a required file - if filename not in metadata.requires_files: - metadata.requires_files.append(filename) - value = '\n'.join(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, split_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 = {} - # bookkeeping for the loop below - firstline = True - prev = None - - for line in files.get('package_data', []): - if '=' in line: - # package name -- file globs or specs - key, value = line.split('=') - prev = self.dist.package_data[key.strip()] = value.split() - elif firstline: - # invalid continuation on the first line - raise PackagingOptionError( - 'malformed package_data first line: %r (misses "=")' % - line) - else: - # continuation, add to last seen package name - prev.extend(line.split()) - - firstline = False - - 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: - # no str.partition in 2.4 :( - labels = section_key.split(':') - if len(labels) == 2 and labels[0] == 'extension': - values_dct = content[section_key] - if 'name' in values_dct: - raise PackagingOptionError( - 'extension name should be given as [extension: name], ' - 'not as key') - name = labels[1].strip() - _check_name(name, self.dist.packages) - ext_modules.append(Extension( - 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, encoding='utf-8') - - 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 = split_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 = split_multiline(compilers) - if isinstance(compilers, str): - compilers = [compilers] - for compiler in compilers: - set_compiler(compiler.strip()) - - def _load_commands(self, commands): - commands = split_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 deleted file mode 100644 index 3d45ca9f90e0..000000000000 --- a/Lib/packaging/create.py +++ /dev/null @@ -1,682 +0,0 @@ -"""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*. -""" - -# 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 re -import imp -import sys -import glob -import shutil -import sysconfig -from hashlib import md5 -from textwrap import dedent -from tokenize import detect_encoding -from configparser import RawConfigParser - -from packaging import logger -# 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' # FIXME use a section in user .pydistutils.cfg - -_helptext = { - 'name': ''' -The name of the project to be packaged, usually a single word composed -of lower-case characters such as "zope.interface", "sqlalchemy" or -"CherryPy". -''', - 'version': ''' -Version number of the software, typically 2 or 3 numbers separated by -dots such as "1.0", "0.6b3", or "3.2.1". "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': ''' -Email address of the project author. -''', - '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': ''' -Python packages included in the project. -''', - 'modules': ''' -Pure Python modules included in the 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 a public Web page. -''', - '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. -An interactive helper 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", "rb") as f: - encoding, lines = detect_encoding(f.readline) - with open("setup.py", encoding=encoding) 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 ('y', 'n'): - return answer[0].lower() - - logger.error('You must select "Y" or "N".') - - -# XXX use util.ask -# FIXME: if prompt ends with '?', don't add ':' - - -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: - line = input(prompt).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 subkey not 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 existence 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): - message = ("ERROR: %(name)s.old backup exists, please check " - "that current %(name)s is correct and remove " - "%(name)s.old" % {'name': _FILENAME}) - logger.error(message) - return - shutil.move(_FILENAME, '%s.old' % _FILENAME) - - with open(_FILENAME, 'w', encoding='utf-8') as fp: - fp.write('[metadata]\n') - # TODO use metadata module instead of hard-coding field-specific - # behavior here - - # 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']: - # XXX shoud use comma to separate, not space - 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', '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())) - - if self.data.get('package_data'): - fp.write('package_data =\n') - for pkg, spec in sorted(self.data['package_data'].items()): - # put one spec per line, indented under the package name - indent = ' ' * (len(pkg) + 7) - spec = ('\n' + indent).join(spec) - fp.write(' %s = %s\n' % (pkg, spec)) - fp.write('\n') - - if self.data.get('resources'): - fp.write('resources =\n') - for src, dest in self.data['resources']: - fp.write(' %s = %s\n' % (src, dest)) - fp.write('\n') - - os.chmod(_FILENAME, 0o644) - logger.info('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.""" - - # TODO use config and metadata instead of Distribution - 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'), - ('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 = sysconfig.get_paths(vars=vars).items() - # sort tokens to use the longest one first - path_tokens = sorted(path_tokens, key=lambda x: len(x[1])) - for dest, srcs in (dist.data_files or []): - dest = os.path.join(sys.prefix, dest) - dest = dest.replace(os.path.sep, '/') - for tok, path in path_tokens: - path = path.replace(os.path.sep, '/') - if not dest.startswith(path): - continue - - dest = ('{%s}' % tok) + dest[len(path):] - files = [('/ '.join(src.rsplit('/', 1)), dest) - for src in srcs] - data['resources'].extend(files) - - # 2.2 package_data - data['package_data'] = dist.package_data.copy() - - # 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, encoding='utf-8') as fp: - contents = fp.read() - contents = re.sub('\s', '', contents.lower()).encode() - val = md5(contents).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(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 testing! - if not is_valid_version(self.data['version']): - msg = "Invalid version discovered: %s" % self.data['version'] - raise ValueError(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('Project description 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 email 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: - logger.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: - logger.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): - logger.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() diff --git a/Lib/packaging/database.py b/Lib/packaging/database.py deleted file mode 100644 index e028dc55fb1d..000000000000 --- a/Lib/packaging/database.py +++ /dev/null @@ -1,651 +0,0 @@ -"""PEP 376 implementation.""" - -import os -import re -import csv -import sys -import zipimport -from io import StringIO -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', - # XXX these functions' names look like get_file_users but are not related - 'get_file_path', 'get_file'] - - -# 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_generated, _cache_generated_egg - - _cache_name.clear() - _cache_name_egg.clear() - _cache_path.clear() - _cache_path_egg.clear() - _cache_generated = False - _cache_generated_egg = False - - -def _yield_distributions(include_dist, include_egg, paths): - """ - 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, paths): - 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 dist.name not in _cache_name: - _cache_name[dist.name] = [] - _cache_name[dist.name].append(dist) - else: - _cache_path_egg[dist.path] = dist - if dist.name not 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 path not in _cache_path: - _cache_path[path] = self - - def __repr__(self): - return '' % ( - self.name, self.version, self.path) - - def _get_records(self, local=False): - results = [] - with self.get_distinfo_file('RECORD') as record: - record_reader = csv.reader(record, delimiter=',', - lineterminator='\n') - for row in record_reader: - missing = [None for i in range(len(row), 3)] - path, checksum, size = row + missing - if local: - path = path.replace('/', os.sep) - path = os.path.join(sys.prefix, path) - results.append((path, checksum, size)) - return results - - def get_resource_path(self, relative_path): - with self.get_distinfo_file('RESOURCES') as resources_file: - resources_reader = csv.reader(resources_file, delimiter=',', - lineterminator='\n') - 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 as a local - absolute path - - :type local: boolean - :returns: iterator of (path, md5, size) - """ - for result in self._get_records(local): - yield result - - 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): - # XXX add separator or use real relpath algo - if path.startswith(self.path): - 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 = 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=None): - """ - 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 paths is None: - paths = sys.path - - 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 is 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 - - -def get_file_path(distribution_name, relative_path): - """Return the path to a resource file.""" - dist = get_distribution(distribution_name) - if dist is not 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/depgraph.py b/Lib/packaging/depgraph.py deleted file mode 100644 index d633b636ad8c..000000000000 --- a/Lib/packaging/depgraph.py +++ /dev/null @@ -1,270 +0,0 @@ -"""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 x not 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 '%r %s' % (dist.name, dist.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.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 %r has ill-formed' - 'provides field: %r' % (dist.name, p)) - version = version[1:-1] # trim off parenthesis - if name not 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 name not 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 dist not in dists: - raise ValueError('given distribution %r is not a member of the list' % - dist.name) - 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 prev not in dep: - fringe.append(prev) - - dep.pop(0) # remove dist from dep, was there to prevent infinite loops - return dep - - -def main(): - # XXX move to run._graph - 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') - print(tempout) - print(e) - sys.exit(1) - - for dist, reqs in graph.missing.items(): - if len(reqs) > 0: - print("Warning: Missing dependencies for %r:" % 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 %r' % filename) - sys.exit(0) - else: - print('Supported option: -d [filename]') - sys.exit(1) diff --git a/Lib/packaging/dist.py b/Lib/packaging/dist.py deleted file mode 100644 index 607767e971ea..000000000000 --- a/Lib/packaging/dist.py +++ /dev/null @@ -1,769 +0,0 @@ -"""Class representing the project being built/installed/etc.""" - -import os -import re - -from packaging import logger -from packaging.util import strtobool, resolve_name -from packaging.config import Config -from packaging.errors import (PackagingOptionError, PackagingArgError, - PackagingModuleError, PackagingClassError) -from packaging.command import get_command_class, STANDARD_COMMANDS -from packaging.command.cmd import Command -from packaging.metadata import Metadata -from packaging.fancy_getopt import FancyGetopt - -# Regex to define acceptable Packaging command names. This is not *quite* -# the same as a Python name -- leading underscores are not allowed. 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: - """Class used to represent a project and work with it. - - Most of the work hiding behind 'pysetup run' is really done within a - Distribution instance, which farms the work out to the commands - specified on the command line. - """ - - # 'global_options' describes the command-line options that may be - # supplied to the setup script prior to any actual commands. - # Eg. "pysetup run -n" or "pysetup run --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) - - pysetup run build will build the project underneath 'build/' - pysetup run install will install the project -""" - - # options that are not propagated to the commands - display_options = [ - ('help-commands', None, - "list all available commands"), - ('use-2to3', None, - "use 2to3 to make source python 3.x compatible"), - ('convert-2to3-doctests', None, - "use 2to3 to convert doctests in separate 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, filesafe=False): - return self.metadata.get_fullname(filesafe) - - 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) - 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. - # "pysetup run --help" and "pysetup run --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 True - - 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 callable(func): - 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: --help-commands. Finally, - lists per-command help for every command name or command class - in 'commands'. - """ - 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) 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 True - - # 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 - 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 = 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. (XXX support for error reporting is suboptimal - # here: 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 reinitialize_command(self, command, reinit_subcommands=False): - """Reinitializes a command to the state it was in when first - returned by 'get_command_obj()': i.e., 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, i.e. those - whose test predicate return true. - - Returns the reinitialized command object. It will be the same - object as the one stored in the self.command_obj attribute. - """ - 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.reinitialize_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 callable(hook_obj): - 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 deleted file mode 100644 index 88781296090b..000000000000 --- a/Lib/packaging/errors.py +++ /dev/null @@ -1,138 +0,0 @@ -"""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 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 deleted file mode 100644 index 61dd5fc58edb..000000000000 --- a/Lib/packaging/fancy_getopt.py +++ /dev/null @@ -1,388 +0,0 @@ -"""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 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: - longopt, short, help = option - repeat = 0 - elif len(option) == 4: - longopt, 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(longopt, str) or len(longopt) < 2: - raise PackagingGetoptError( - ("invalid long option '%s': " - "must be a string of length >= 2") % longopt) - - 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[longopt] = repeat - self.long_opts.append(longopt) - - if longopt[-1] == '=': # option takes an argument? - if short: - short = short + ':' - longopt = longopt[0:-1] - self.takes_arg[longopt] = 1 - else: - - # Is option is a "negative alias" for some other option (eg. - # "quiet" == "!verbose")? - alias_to = self.negative_alias.get(longopt) - if alias_to is not None: - if self.takes_arg[alias_to]: - raise PackagingGetoptError( - ("invalid negative alias '%s': " - "aliased option '%s' takes a value") % \ - (longopt, alias_to)) - - self.long_opts[-1] = longopt # XXX redundant?! - self.takes_arg[longopt] = 0 - - else: - self.takes_arg[longopt] = 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(longopt) - if alias_to is not None: - if self.takes_arg[longopt] != 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") % (longopt, 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(longopt): - raise PackagingGetoptError( - ("invalid long option name '%s' " + - "(must be letters, numbers, hyphens only") % longopt) - - self.attr_name[longopt] = longopt.replace('-', '_') - if short: - self.short_opts.append(short) - self.short2long[short[0]] = longopt - - 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: - longopt = option[0] - short = option[1] - l = len(longopt) - if longopt[-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: - longopt, 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, longopt, text[0])) - else: - lines.append(" --%-*s " % (max_opt, longopt)) - - # Case 2: we have a short option, so we have to include it - # just after the long option - else: - opt_names = "%s (-%s)" % (longopt, 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) - - -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 deleted file mode 100644 index 776ba4014c3c..000000000000 --- a/Lib/packaging/install.py +++ /dev/null @@ -1,529 +0,0 @@ -"""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, get_path, is_python_build - -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 -from packaging import database - - -__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. - """ - - for old in files: - filename = os.path.split(old)[-1] - new = os.path.join(destination, filename) - # try to make the paths. - try: - os.makedirs(os.path.dirname(new)) - except OSError as e: - if e.errno != errno.EEXIST: - raise - 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') - name = dist.metadata['Name'] - return database.get_distribution(name) is not None - except (IOError, os.error, PackagingError, CCompilerError) as msg: - raise ValueError("Failed to install, " + 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. - - Returns True on success, False on Failure. - """ - path = os.path.abspath(path) - if os.path.isdir(path): - logger.info('Installing from source directory: %r', path) - return _run_install_from_dir(path) - elif _is_archive_file(path): - logger.info('Installing from archive: %r', path) - _unpacked_dir = tempfile.mkdtemp() - try: - shutil.unpack_archive(path, _unpacked_dir) - return _run_install_from_archive(_unpacked_dir) - finally: - shutil.rmtree(_unpacked_dir) - else: - logger.warning('No project to install.') - return False - - -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] - try: - func(source_dir) - return True - except ValueError as err: - # failed to install - logger.info(str(err)) - return False - finally: - os.chdir(old_dir) - - -def install_dists(dists, path, paths=None): - """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 - """ - - installed_dists = [] - for dist in dists: - logger.info('Installing %r %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 %r', installed_dist) - remove(installed_dist.name, paths) - raise e - return installed_dists - - -def install_from_infos(install_path=None, install=[], remove=[], conflicts=[], - paths=None): - """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.debug('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 %r %s', installed_project.name, - installed_project.version) - - # if we already have something installed, check it matches the - # requirements - if predicate.match(installed_project.version): - return infos - break - - if not found: - logger.debug('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: %r' % 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(project_name, paths=None, auto_confirm=True): - """Removes a single project from the installation. - - Returns True on success - """ - dist = get_distribution(project_name, use_egg_info=True, paths=paths) - if dist is None: - raise PackagingError('Distribution %r not found' % project_name) - files = dist.list_installed_files(local=True) - rmdirs = [] - rmfiles = [] - tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall') - - def _move_file(source, target): - try: - os.rename(source, target) - except OSError as err: - return err - return None - - success = True - error = None - 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: - error = _move_file(file_, tmpfile) - if error is not None: - success = False - break - 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) - - if not success: - logger.info('%r cannot be removed.', project_name) - logger.info('Error: %s', error) - return False - - 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) - - return True - - -def install(project): - """Installs a project. - - Returns True on success, False on failure - """ - if is_python_build(): - # Python would try to install into the site-packages directory under - # $PREFIX, but when running from an uninstalled code checkout we don't - # want to create directories under the installation root - message = ('installing third-party projects from an uninstalled ' - 'Python is not supported') - logger.error(message) - return False - - logger.info('Checking the installation location...') - purelib_path = get_path('purelib') - - # trying to write a file there - try: - with tempfile.NamedTemporaryFile(suffix=project, - dir=purelib_path) as testfile: - testfile.write(b'test') - except OSError: - # FIXME this should check the errno, or be removed altogether (race - # condition: the directory permissions could be changed between here - # and the actual install) - logger.info('Unable to write in "%s". Do you have the permissions ?' - % purelib_path) - return False - - logger.info('Getting information about %r...', project) - try: - info = get_infos(project) - except InstallationException: - logger.info('Cound not find %r', project) - return False - - if info['install'] == []: - logger.info('Nothing to install') - return False - - 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 = ('%r %s' % (p.name, p.version) for p in e.args[0]) - logger.info('%r conflicts with %s', project, ','.join(projects)) - - return True diff --git a/Lib/packaging/manifest.py b/Lib/packaging/manifest.py deleted file mode 100644 index 40e733013a43..000000000000 --- a/Lib/packaging/manifest.py +++ /dev/null @@ -1,381 +0,0 @@ -"""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 and words[0] not in ( - 'include', 'exclude', 'global-include', 'global-exclude', - 'recursive-include', 'recursive-exclude', 'graft', 'prune'): - # 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. - """ - # XXX docstring lying about what the special chars are? - 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 (currently: just os.sep). - sep = os.sep - if os.sep == '\\': - # we're using a regex to manipulate a regex, so we need - # to escape the backslash twice - sep = r'\\\\' - escaped = r'\1[^%s]' % sep - 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': '%s.%s' % sys.version_info[:2], - # FIXME parsing sys.platform is not reliable, but there is no other - # way to get e.g. 2.7.2+, and the PEP is defined with sys.version - '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 deleted file mode 100644 index 2993ebbe7189..000000000000 --- a/Lib/packaging/metadata.py +++ /dev/null @@ -1,570 +0,0 @@ -"""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 = [] - super(SilentReporter, self).__init__( - 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', 'Classifier', - 'Download-URL') - -_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() - -_FILESAFE = re.compile('[^A-Za-z0-9.]+') - - -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, filesafe=False): - """Return the distribution name with version. - - If filesafe is true, return a filename-escaped form.""" - name, version = self['Name'], self['Version'] - if filesafe: - # For both name and version any runs of non-alphanumeric or '.' - # characters are replaced with a single '-'. Additionally any - # spaces in the version string become '.' - name = _FILESAFE.sub('-', name) - version = _FILESAFE.sub('-', version.replace(' ', '.')) - return '%s-%s' % (name, 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='utf-8') 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', encoding='utf-8') 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. - """ - # XXX the code should just use self.set, which does tbe same checks and - # conversions already, but that would break packaging.pypi: it uses the - # update method, which does not call _set_best_version (which set - # does), and thus allows having a Metadata object (as long as you don't - # modify or write it) with extra fields from PyPI that are not fields - # defined in Metadata PEPs. to solve it, the best_version system - # should be reworked so that it's called only for writing, or in a new - # strict mode, or with a new, more lax Metadata subclass in p7g.pypi - def _set(key, value): - if key in _ATTR2FIELD and value: - self.set(self._convert_name(key), value) - - if not other: - # other is None or empty container - 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: - for k, v in kwargs.items(): - _set(k, v) - - 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): - project_name = self['Name'] - - 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: %r is not a valid predicate (field %r)', - project_name, 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: %r is not a valid version (field %r)', - project_name, value, name) - elif name in _VERSION_FIELDS and value is not None: - if not is_valid_version(value): - logger.warning('%r: %r is not a valid version (field %r)', - project_name, 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 - # XXX these methods should return views or sets in 3.x - - def keys(self): - return list(_version2fieldlist(self['Metadata-Version'])) - - def __iter__(self): - for key in self.keys(): - yield key - - def values(self): - return [self[key] for key in self.keys()] - - def items(self): - return [(key, self[key]) for key in self.keys()] diff --git a/Lib/packaging/pypi/__init__.py b/Lib/packaging/pypi/__init__.py deleted file mode 100644 index 5660c50ade58..000000000000 --- a/Lib/packaging/pypi/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""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 deleted file mode 100644 index 305fca9cc8f1..000000000000 --- a/Lib/packaging/pypi/base.py +++ /dev/null @@ -1,48 +0,0 @@ -"""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 deleted file mode 100644 index 541465e63fb1..000000000000 --- a/Lib/packaging/pypi/dist.py +++ /dev/null @@ -1,544 +0,0 @@ -"""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 re -import hashlib -import tempfile -import urllib.request -import urllib.parse -import urllib.error -import urllib.parse -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 url not 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) - 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 None not 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 version not 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, beta or alpha) would be preferred 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 deleted file mode 100644 index 2191ac100c7d..000000000000 --- a/Lib/packaging/pypi/errors.py +++ /dev/null @@ -1,39 +0,0 @@ -"""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 deleted file mode 100644 index a646acff3cdd..000000000000 --- a/Lib/packaging/pypi/mirrors.py +++ /dev/null @@ -1,52 +0,0 @@ -"""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 deleted file mode 100644 index e26d55d00280..000000000000 --- a/Lib/packaging/pypi/simple.py +++ /dev/null @@ -1,462 +0,0 @@ -"""Spider using the screen-scraping "simple" PyPI API. - -This module contains the class Crawler, 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 functools import wraps -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 - -__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.%s packaging/%s" % ( - sys.version_info[0], sys.version_info[1], 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 wrapper(func): - @wraps(func) - def wrapped(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 wrapped - return wrapper - - -def with_mirror_support(): - """Decorator that makes the mirroring support easier""" - def wrapper(func): - @wraps(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 - addresses. - :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. - parsed = urllib.parse.urlparse(index_url) - self.scheme = parsed[0] - if self.scheme == 'file': - ender = os.path.sep - else: - ender = '/' - if not index_url.endswith(ender): - index_url += ender - # 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. - """ - if '*' in name: - name.replace('*', '.*') - else: - name = "%s%s%s" % ('*.?', name, '*.?') - name = name.replace('*', '[^<]*') # avoid matching end tag - pattern = (']*>(%s)' % name).encode('utf-8') - projectname = re.compile(pattern, re.I) - matching_projects = [] - - with self._open_url(self.index_url) as index: - index_content = index.read() - - for match in projectname.finditer(index_content): - project_name = match.group(1).decode('utf-8') - 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 ReleasesList 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.debug('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() - # XXX use urllib.parse for a real check of missing scheme part - if not index_url.startswith(("http://", "https://", "file://")): - 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 preferred - 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 name.lower() not 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=self.index_url not 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. - if self.scheme == 'file': - ender = os.path.sep - else: - ender = '/' - url = self.index_url + name + ender - 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(os.path.sep): - 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 deleted file mode 100644 index 945d08abb78a..000000000000 --- a/Lib/packaging/pypi/wrapper.py +++ /dev/null @@ -1,99 +0,0 @@ -"""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 deleted file mode 100644 index befdf6dbbb94..000000000000 --- a/Lib/packaging/pypi/xmlrpc.py +++ /dev/null @@ -1,200 +0,0 @@ -"""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 = Client() - >>> client.server_url == DEFAULT_XMLRPC_INDEX_URL - True - - >>> client = Client("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 = Client() - >>> 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 = Client() - >>> 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/run.py b/Lib/packaging/run.py deleted file mode 100644 index c3600a704831..000000000000 --- a/Lib/packaging/run.py +++ /dev/null @@ -1,663 +0,0 @@ -"""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, generate_setup_py -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 -""" - -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("""\ -Usage: pysetup create - or: pysetup create --help - -Create a new Python project. -""") -def _create(distpatcher, args, **kw): - from packaging.create import main - return main() - - -@action_help("""\ -Usage: pysetup generate-setup - or: pysetup generate-setup --help - -Generate a setup.py script for backward-compatibility purposes. -""") -def _generate(distpatcher, args, **kw): - generate_setup_py() - logger.info('The setup.py was generated') - - -@action_help("""\ -Usage: pysetup graph dist - or: pysetup graph --help - -Print dependency graph for the distribution. - -positional arguments: - dist installed distribution name -""") -def _graph(dispatcher, args, **kw): - name = args[1] - dist = get_distribution(name, use_egg_info=True) - if dist is None: - logger.warning('Distribution not found.') - return 1 - else: - dists = get_distributions(use_egg_info=True) - graph = generate_graph(dists) - print(graph.repr_node(dist)) - - -@action_help("""\ -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 -""") -def _install(dispatcher, args, **kw): - # first check if we are in a source directory - if len(args) < 2: - # are we inside a project dir? - if os.path.isfile('setup.cfg') or os.path.isfile('setup.py'): - args.insert(1, os.getcwd()) - else: - logger.warning('No project to install.') - return 1 - - target = args[1] - # installing from a source dir or archive file? - if os.path.isdir(target) or _is_archive_file(target): - return not install_local_project(target) - else: - # download from PyPI - return not install(target) - - -@action_help("""\ -Usage: pysetup metadata [dist] - or: pysetup metadata [dist] [-f field ...] - or: pysetup metadata --help - -Print metadata for the distribution. - -positional arguments: - dist installed distribution name - -optional arguments: - -f metadata field to print; omit to get all fields -""") -def _metadata(dispatcher, args, **kw): - opts = _parse_args(args[1:], 'f:', []) - if opts['args']: - name = opts['args'][0] - dist = get_distribution(name, use_egg_info=True) - if dist is None: - logger.warning('%r not installed', name) - return 1 - elif os.path.isfile('setup.cfg'): - logger.info('searching local dir for metadata') - dist = Distribution() # XXX use config module - dist.parse_config_files() - else: - logger.warning('no argument given and no local setup.cfg found') - return 1 - - metadata = dist.metadata - - if 'f' in opts: - keys = (k for k in opts['f'] if k in metadata) - else: - keys = metadata.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("""\ -Usage: pysetup remove dist [-y] - or: pysetup remove --help - -Uninstall a Python distribution. - -positional arguments: - dist installed distribution name - -optional arguments: - -y auto confirm distribution removal -""") -def _remove(distpatcher, args, **kw): - opts = _parse_args(args[1:], 'y', []) - if 'y' in opts: - auto_confirm = True - else: - auto_confirm = False - - retcode = 0 - for dist in set(opts['args']): - try: - remove(dist, auto_confirm=auto_confirm) - except PackagingError: - logger.warning('%r not installed', dist) - retcode = 1 - - return retcode - - -@action_help("""\ -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 -""") -def _run(dispatcher, args, **kw): - parser = dispatcher.parser - args = args[1:] - - commands = STANDARD_COMMANDS # FIXME display extra commands - - if args == ['--list-commands']: - print('List of available commands:') - for cmd in commands: - 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() - - for cmd in dispatcher.commands: - # FIXME need to catch MetadataMissingError here (from the check command - # e.g.)--or catch any exception, print an error message and exit with 1 - dist.run_command(cmd, dispatcher.command_options[cmd]) - - return 0 - - -@action_help("""\ -Usage: pysetup list [dist ...] - or: pysetup list --help - -Print name, version and location for the matching installed distributions. - -positional arguments: - dist installed distribution name; omit to get all distributions -""") -def _list(dispatcher, args, **kw): - opts = _parse_args(args[1:], '', []) - dists = get_distributions(use_egg_info=True) - if opts['args']: - results = (d for d in dists if d.name.lower() in opts['args']) - listall = False - else: - results = dists - listall = True - - number = 0 - for dist in results: - print('%r %s (from %r)' % (dist.name, dist.version, dist.path)) - number += 1 - - if number == 0: - if listall: - logger.info('Nothing seems to be installed.') - else: - logger.warning('No matching distribution found.') - return 1 - else: - logger.info('Found %d projects installed.', number) - - -@action_help("""\ -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] whether to use the xmlrpc index or not. If an url is - specified, it will be used rather than the default one. - - --simple [url] whether 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. -""") -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) - logger.error('not implemented') - return 1 - - -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', 'List installed projects', _list), - ('graph', 'Display a graph', _graph), - ('create', 'Create a project', _create), - ('generate-setup', 'Generate a backward-compatible setup.py', _generate), -] - - -class Dispatcher: - """Reads the command-line options - """ - def __init__(self, args=None): - self.verbose = 1 - self.dry_run = False - self.help = False - 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) - - self._set_logger() - self.args = args - - # for display options we return immediately - if self.help or self.action is None: - self._show_help(self.parser, display_options_=False) - - def _set_logger(self): - # 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 - - # setting up the stream handler - handler = logging.StreamHandler(sys.stderr) - handler.setLevel(level) - logger.addHandler(handler) - logger.setLevel(level) - - 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 callable(func): - 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) - - desc = getattr(command, 'description', '(no description available)') - print('Description:', 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): - old_level = logger.level - old_handlers = list(logger.handlers) - try: - dispatcher = Dispatcher(args) - if dispatcher.action is None: - return - return dispatcher() - except KeyboardInterrupt: - logger.info('interrupted') - return 1 - except (IOError, os.error, PackagingError, CCompilerError) as exc: - logger.exception(exc) - return 1 - finally: - logger.setLevel(old_level) - logger.handlers[:] = old_handlers - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/Lib/packaging/tests/LONG_DESC.txt b/Lib/packaging/tests/LONG_DESC.txt deleted file mode 100644 index 2b4358aae6cc..000000000000 --- a/Lib/packaging/tests/LONG_DESC.txt +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index f48546e5a88d..000000000000 --- a/Lib/packaging/tests/PKG-INFO +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index dff8d0056715..000000000000 --- a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO +++ /dev/null @@ -1,182 +0,0 @@ -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 deleted file mode 100644 index 4b3906a41bcc..000000000000 --- a/Lib/packaging/tests/SETUPTOOLS-PKG-INFO2 +++ /dev/null @@ -1,183 +0,0 @@ -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 deleted file mode 100644 index cb820044f431..000000000000 --- a/Lib/packaging/tests/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -"""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. - -Always import unittest from this module: it will be unittest from the -standard library for packaging tests and unittest2 for distutils2 tests. -""" - -import os -import sys -import unittest - - -def test_suite(): - suite = unittest.TestSuite() - here = os.path.dirname(__file__) or os.curdir - 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 diff --git a/Lib/packaging/tests/__main__.py b/Lib/packaging/tests/__main__.py deleted file mode 100644 index 00f323e154bb..000000000000 --- a/Lib/packaging/tests/__main__.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Packaging test suite runner.""" - -# Ripped from importlib tests, thanks Brett! - -import os -import unittest -from test.support import run_unittest, reap_children, reap_threads - - -@reap_threads -def test_main(): - try: - start_dir = os.path.dirname(__file__) - top_dir = os.path.dirname(os.path.dirname(start_dir)) - test_loader = unittest.TestLoader() - # XXX find out how to use unittest.main, to get command-line options - # (failfast, catch, etc.) - run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir)) - finally: - 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 65e839a01fae..000000000000 --- a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/METADATA +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 5d0da494a5e2..000000000000 --- a/Lib/packaging/tests/fake_dists/babar-0.1.dist-info/RESOURCES +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index ecd6efe9a691..000000000000 --- a/Lib/packaging/tests/fake_dists/babar.cfg +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index a176dfdfde16..000000000000 --- a/Lib/packaging/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index a7e118a88f3b..000000000000 --- a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 8b137891791f..000000000000 --- a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - 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 deleted file mode 100644 index 5d3e5f68c785..000000000000 --- a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt +++ /dev/null @@ -1,3 +0,0 @@ - - # -*- 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 deleted file mode 100644 index 8b137891791f..000000000000 --- a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - 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 deleted file mode 100644 index 4354305659c9..000000000000 --- a/Lib/packaging/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt +++ /dev/null @@ -1,6 +0,0 @@ -# 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 27cbe3014dd0..000000000000 --- a/Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 418929eccae1..000000000000 --- a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 40a96afc6ff0..000000000000 --- a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- 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 deleted file mode 100644 index c4027f36c1ad..000000000000 --- a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 deleted file mode 100644 index 342b8ea851df..000000000000 --- a/Lib/packaging/tests/fake_dists/choxie-2.0.0.9/truffles.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- 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 deleted file mode 100644 index 499a083e40fa..000000000000 --- a/Lib/packaging/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 0b99f5249a1d..000000000000 --- a/Lib/packaging/tests/fake_dists/grammar-1.0a4.dist-info/METADATA +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 40a96afc6ff0..000000000000 --- a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- 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 deleted file mode 100644 index 66ba796c3d14..000000000000 --- a/Lib/packaging/tests/fake_dists/grammar-1.0a4/grammar/utils.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- 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 deleted file mode 100644 index 0c58ec1ce92b..000000000000 --- a/Lib/packaging/tests/fake_dists/nut-funkyversion.egg-info +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 6d160e8b161031ae52638514843592187925b757..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 1402 zc-m7|)KALH(=X28%1l#;R!B%nEKbc!%uQ8LF-TCbRZuEUEh#N1$r9UQWv|0ty0Ufu2)H%gjmDgJ}xL0otCbPy`8@OrXVy z$=M1e`3iV~M*-+)g_5F5g~as4%sjABpm0h{1UV)xlPkcRnT3l11j?rGw_!j6Vhl12 zuI}!-o_=or`X%`V@j0nwsX2Nj6(yk|oD9qiubF*7xU_i9RnLxBqqeU~_GC)B*8Q%^m+PITr6Z&8t31F+o4>xK^{d-p7?^ zx~J#&ZkWuS9 any* > - """ - - def transform(self, node, results): - name = results['name'] - name.replace(Name('print', prefix=name.prefix)) diff --git a/Lib/packaging/tests/fixer/fix_echo2.py b/Lib/packaging/tests/fixer/fix_echo2.py deleted file mode 100644 index 1b92891b025f..000000000000 --- a/Lib/packaging/tests/fixer/fix_echo2.py +++ /dev/null @@ -1,16 +0,0 @@ -# Example custom fixer, derived from fix_raw_input by Andre Roberge - -from lib2to3 import fixer_base -from lib2to3.fixer_util import Name - - -class FixEcho2(fixer_base.BaseFix): - - BM_compatible = True - PATTERN = """ - power< name='echo2' trailer< '(' [any] ')' > any* > - """ - - def transform(self, node, results): - name = results['name'] - name.replace(Name('print', prefix=name.prefix)) diff --git a/Lib/packaging/tests/pypi_server.py b/Lib/packaging/tests/pypi_server.py deleted file mode 100644 index 13c30cf40b1e..000000000000 --- a/Lib/packaging/tests/pypi_server.py +++ /dev/null @@ -1,449 +0,0 @@ -"""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.fulladdress() - "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 threading -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=None, - 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. - super(PyPIServer, self).__init__() - self._run = True - self._serve_xmlrpc = serve_xmlrpc - if static_filesystem_paths is None: - static_filesystem_paths = ["default"] - - #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 = ('127.0.0.1', 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 - if self.is_alive(): - self.join() - self.server.server_close() - - 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, 'rb') 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')] - - headers.append(('Content-Length', len(data))) - 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 isinstance(data, str): - data = data.encode('utf-8') - - self.wfile.write(data) - - -class PyPIXMLRPCServer(SimpleXMLRPCServer): - def server_bind(self): - """Override server_bind to store the server name.""" - super(PyPIXMLRPCServer, self).server_bind() - host, port = self.socket.getsockname()[:2] - 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, # XXX licence or license? - 'summary': self.summary, - 'home_page': self.homepage, - 'stable_version': self.stable_version, - # FIXME doesn't that reproduce the bug from 6527d3106e9f? - '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 deleted file mode 100644 index 8c8c641eb0d4..000000000000 --- a/Lib/packaging/tests/pypi_test_server.py +++ /dev/null @@ -1,59 +0,0 @@ -"""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 deleted file mode 100644 index 333961eb18a6e7db80fefd41c339ab218d5180c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc-jL100001 literal 110 zc-oWi=3uy!>FUeC{PvtR-ysJc)&sVu?9yZ7`(A1Di)P(6s!I71JWZ;--fWND`LA)=lAmk-7Jbj=XMlnFEsQ#U Kd|Vkc7#IK&xGYxy 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 deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index b89f1bdb8468..000000000000 --- a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/badmd5/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -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 deleted file mode 100644 index 9e42b16d23c7..000000000000 --- a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/foobar/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -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 deleted file mode 100644 index 9baee0479e9b..000000000000 --- a/Lib/packaging/tests/pypiserver/downloads_with_md5/simple/index.html +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index c3d42c56925b..000000000000 --- a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/bar/index.html +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 4f34312a30c9..000000000000 --- a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/baz/index.html +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 0565e11bddcf..000000000000 --- a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/foo/index.html +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index a70cfd345fbc..000000000000 --- a/Lib/packaging/tests/pypiserver/foo_bar_baz/simple/index.html +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index b36d728a0bbd..000000000000 --- a/Lib/packaging/tests/pypiserver/project_list/simple/index.html +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index a282a4ea3108..000000000000 --- a/Lib/packaging/tests/pypiserver/test_found_links/simple/foobar/index.html +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index a1a7bb72825d..000000000000 --- a/Lib/packaging/tests/pypiserver/test_found_links/simple/index.html +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 265ee0af9509..000000000000 --- a/Lib/packaging/tests/pypiserver/test_pypi_server/external/index.html +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 6f976676f559..000000000000 --- a/Lib/packaging/tests/pypiserver/test_pypi_server/simple/index.html +++ /dev/null @@ -1 +0,0 @@ -Yeah diff --git a/Lib/packaging/tests/pypiserver/with_externals/external/external.html b/Lib/packaging/tests/pypiserver/with_externals/external/external.html deleted file mode 100644 index 92e4702f634d..000000000000 --- a/Lib/packaging/tests/pypiserver/with_externals/external/external.html +++ /dev/null @@ -1,3 +0,0 @@ - -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 deleted file mode 100644 index b100a26542ea..000000000000 --- a/Lib/packaging/tests/pypiserver/with_externals/simple/foobar/index.html +++ /dev/null @@ -1,4 +0,0 @@ - -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 deleted file mode 100644 index a1a7bb72825d..000000000000 --- a/Lib/packaging/tests/pypiserver/with_externals/simple/index.html +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 1cc0c32f1bb8..000000000000 --- a/Lib/packaging/tests/pypiserver/with_norel_links/external/homepage.html +++ /dev/null @@ -1,7 +0,0 @@ - - -

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 deleted file mode 100644 index f6ace2205488..000000000000 --- a/Lib/packaging/tests/pypiserver/with_norel_links/external/nonrel.html +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index 171df9360cef..000000000000 --- a/Lib/packaging/tests/pypiserver/with_norel_links/simple/foobar/index.html +++ /dev/null @@ -1,6 +0,0 @@ - -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 deleted file mode 100644 index a1a7bb72825d..000000000000 --- a/Lib/packaging/tests/pypiserver/with_norel_links/simple/index.html +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index b2885ae384a2..000000000000 --- a/Lib/packaging/tests/pypiserver/with_real_externals/simple/foobar/index.html +++ /dev/null @@ -1,4 +0,0 @@ - -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 deleted file mode 100644 index a1a7bb72825d..000000000000 --- a/Lib/packaging/tests/pypiserver/with_real_externals/simple/index.html +++ /dev/null @@ -1 +0,0 @@ -foobar/ diff --git a/Lib/packaging/tests/support.py b/Lib/packaging/tests/support.py deleted file mode 100644 index d76d3dbdee8c..000000000000 --- a/Lib/packaging/tests/support.py +++ /dev/null @@ -1,400 +0,0 @@ -"""Support code for packaging test cases. - -*This module should not be considered public: its content and API may -change in incompatible ways.* - -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, for example to fake -compilation in build_ext (this requires that the mock build_ext command -be injected into the distribution object's command_obj dictionary). - -For tests that need to compile an extension module, use the -copy_xxmodule_c and fixup_build_ext functions. - -Each class or function has a docstring to explain its purpose and usage. -Existing tests should also be used as examples. -""" - -import os -import sys -import shutil -import logging -import weakref -import tempfile -import sysconfig - -from packaging.dist import Distribution -from packaging.util import resolve_name -from packaging.command import set_command, _COMMANDS - -from packaging.tests import unittest -from test.support import requires_zlib, unlink - -# define __all__ to make pydoc more useful -__all__ = [ - # TestCase mixins - 'LoggingCatcher', 'TempdirManager', 'EnvironRestorer', - # mocks - 'DummyCommand', 'TestDistribution', 'Inputs', - # misc. functions and decorators - 'fake_dec', 'create_distribution', 'use_command', - 'copy_xxmodule_c', 'fixup_build_ext', - 'skip_2to3_optimize', - # imported from this module for backport purposes - 'unittest', 'requires_zlib', 'skip_unless_symlink', -] - - -logger = logging.getLogger('packaging') -logger2to3 = logging.getLogger('RefactoringTool') - - -class _TestHandler(logging.handlers.BufferingHandler): - # stolen and adapted from test.support - - def __init__(self): - super(_TestHandler, self).__init__(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. get_logs automatically flushes the logs, unless you pass - *flush=False*, for example to make multiple calls to the method with - different level arguments. If your test calls some code that generates - logging message and then you don't call get_logs, you will need to flush - manually before testing other code in the same test_* method, otherwise - get_logs in the next lines will see messages from the previous lines. - See example in test_command_check. - """ - - def setUp(self): - super(LoggingCatcher, self).setUp() - self.loghandler = handler = _TestHandler() - self._old_levels = logger.level, logger2to3.level - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) # we want all messages - logger2to3.setLevel(logging.CRITICAL) # we don't want 2to3 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 - logger.setLevel(self._old_levels[0]) - logger2to3.setLevel(self._old_levels[1]) - super(LoggingCatcher, self).tearDown() - - def get_logs(self, level=logging.WARNING, flush=True): - """Return all log messages with given level. - - *level* defaults to logging.WARNING. - - For log calls with arguments (i.e. logger.info('bla bla %r', arg)), - the messages will be formatted before being returned (e.g. "bla bla - 'thing'"). - - Returns a list. Automatically flushes the loghandler after being - called, unless *flush* is False (this is useful to get e.g. all - warnings then all info messages). - """ - messages = [log.getMessage() for log in self.loghandler.buffer - if log.levelno == level] - if flush: - self.loghandler.flush() - return messages - - -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._olddir = os.getcwd() - self._basetempdir = tempfile.mkdtemp() - self._files = [] - - def tearDown(self): - for handle, name in self._files: - handle.close() - unlink(name) - - os.chdir(self._olddir) - shutil.rmtree(self._basetempdir) - super(TempdirManager, self).tearDown() - - def mktempfile(self): - """Create a read-write temporary file and return it.""" - fd, fn = tempfile.mkstemp(dir=self._basetempdir) - os.close(fd) - fp = open(fn, 'w+') - self._files.append((fp, 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', encoding=None): - """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) - with open(path, 'w', encoding=encoding) as f: - f.write(content) - - 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. - """ - # XXX does not work with dist.reinitialize_command, which typechecks - # and wants a finalized attribute - - 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 - - -class Inputs: - """Fakes user inputs.""" - # TODO document usage - # TODO use context manager or something for auto cleanup - - def __init__(self, *answers): - self.answers = answers - self.index = 0 - - def __call__(self, prompt=''): - try: - return self.answers[self.index] - finally: - self.index += 1 - - -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 - - -def use_command(testcase, fullname): - """Register command at *fullname* for the duration of a test.""" - set_command(fullname) - # XXX maybe set_command should return the class object - name = resolve_name(fullname).get_command_name() - # XXX maybe we need a public API to remove commands - testcase.addCleanup(_COMMANDS.__delitem__, name) - - -def fake_dec(*args, **kw): - """Fake decorator""" - def _wrap(func): - def __wrap(*args, **kw): - return func(*args, **kw) - return __wrap - return _wrap - - -def copy_xxmodule_c(directory): - """Helper for tests that need the xxmodule.c source file. - - Example use: - - def test_compile(self): - copy_xxmodule_c(self.tmpdir) - self.assertIn('xxmodule.c', os.listdir(self.tmpdir)) - - If the source file can be found, it will be copied to *directory*. If not, - the test will be skipped. Errors during copy are not caught. - """ - filename = _get_xxmodule_path() - if filename is None: - raise unittest.SkipTest('cannot find xxmodule.c') - shutil.copy(filename, directory) - - -def _get_xxmodule_path(): - if sysconfig.is_python_build(): - srcdir = sysconfig.get_config_var('projectbase') - path = os.path.join(os.getcwd(), srcdir, 'Modules', 'xxmodule.c') - else: - path = os.path.join(os.path.dirname(__file__), 'xxmodule.c') - if os.path.exists(path): - return path - - -def fixup_build_ext(cmd): - """Function needed to make build_ext tests pass. - - When Python was built with --enable-shared on Unix, -L. is not enough to - find libpython.so, because regrtest runs in a tempdir, not in the - source directory where the .so lives. (Mac OS X embeds absolute paths - to shared libraries into executables, so the fixup is a no-op on that - platform.) - - When Python was built with in debug mode on Windows, build_ext commands - need their debug attribute set, and it is not done automatically for - some reason. - - This function handles both of these things, and also fixes - cmd.distribution.include_dirs if the running Python is an uninstalled - build. Example use: - - cmd = build_ext(dist) - support.fixup_build_ext(cmd) - cmd.ensure_finalized() - """ - if os.name == 'nt': - cmd.debug = sys.executable.endswith('_d.exe') - elif sysconfig.get_config_var('Py_ENABLE_SHARED'): - # To further add to the shared builds fun on Unix, we can't just add - # library_dirs to the Extension() instance because that doesn't get - # plumbed through to the final compiler command. - runshared = sysconfig.get_config_var('RUNSHARED') - if runshared is None: - cmd.library_dirs = ['.'] - else: - if sys.platform == 'darwin': - cmd.library_dirs = [] - else: - name, equals, value = runshared.partition('=') - cmd.library_dirs = value.split(os.pathsep) - - # Allow tests to run with an uninstalled Python - if sysconfig.is_python_build(): - pysrcdir = sysconfig.get_config_var('projectbase') - cmd.distribution.include_dirs.append(os.path.join(pysrcdir, 'Include')) - - -try: - from test.support import skip_unless_symlink -except ImportError: - skip_unless_symlink = unittest.skip( - 'requires test.support.skip_unless_symlink') - -skip_2to3_optimize = unittest.skipIf(sys.flags.optimize, - "2to3 doesn't work under -O") diff --git a/Lib/packaging/tests/test_ccompiler.py b/Lib/packaging/tests/test_ccompiler.py deleted file mode 100644 index dd4bdd9d95b6..000000000000 --- a/Lib/packaging/tests/test_ccompiler.py +++ /dev/null @@ -1,15 +0,0 @@ -"""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 deleted file mode 100644 index 7b2ea013ab38..000000000000 --- a/Lib/packaging/tests/test_command_bdist.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Tests for distutils.command.bdist.""" -import os -from test.support import captured_stdout -from packaging.command.bdist import bdist, show_formats -from packaging.tests import unittest, support - - -class BuildTestCase(support.TempdirManager, - support.LoggingCatcher, - unittest.TestCase): - - def test_formats(self): - # let's create a command and make sure - # we can set the format - dist = self.create_dist()[1] - cmd = bdist(dist) - cmd.formats = ['msi'] - cmd.ensure_finalized() - self.assertEqual(cmd.formats, ['msi']) - - # what formats does bdist offer? - # XXX hard-coded lists are not the best way to find available bdist_* - # commands; we should add a registry - formats = ['bztar', 'gztar', 'msi', 'tar', 'wininst', 'zip'] - found = sorted(cmd.format_command) - self.assertEqual(found, formats) - - def test_skip_build(self): - # bug #10946: bdist --skip-build should trickle down to subcommands - dist = self.create_dist()[1] - cmd = bdist(dist) - cmd.skip_build = True - cmd.ensure_finalized() - dist.command_obj['bdist'] = cmd - - names = ['bdist_dumb', 'bdist_wininst'] - if os.name == 'nt': - names.append('bdist_msi') - - for name in names: - subcmd = cmd.get_finalized_command(name) - self.assertTrue(subcmd.skip_build, - '%s should take --skip-build from bdist' % name) - - def test_show_formats(self): - with captured_stdout() as stdout: - show_formats() - stdout = stdout.getvalue() - - # 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 deleted file mode 100644 index 15cf6586e4d5..000000000000 --- a/Lib/packaging/tests/test_command_bdist_dumb.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Tests for distutils.command.bdist_dumb.""" - -import os -import imp -import sys -import zipfile -import packaging.util - -from packaging.dist import Distribution -from packaging.command.bdist_dumb import bdist_dumb -from packaging.tests import unittest, support -from packaging.tests.support import requires_zlib - - -class BuildDumbTestCase(support.TempdirManager, - support.LoggingCatcher, - unittest.TestCase): - - def setUp(self): - super(BuildDumbTestCase, self).setUp() - self.old_location = os.getcwd() - - def tearDown(self): - os.chdir(self.old_location) - packaging.util._path_created.clear() - super(BuildDumbTestCase, self).tearDown() - - @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, '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'], - 'home_page': 'xxx', 'author': 'xxx', - 'author_email': 'xxx'}) - os.chdir(pkg_dir) - 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.zip" % (dist.get_fullname(), cmd.plat_name) - if os.name == 'os2': - base = base.replace(':', '-') - - self.assertEqual(dist_created, [base]) - - # now let's check what we have in the zip file - with zipfile.ZipFile(os.path.join('dist', base)) as fp: - contents = fp.namelist() - - contents = sorted(os.path.basename(fn) for fn in contents) - wanted = ['foo.py', - 'foo.%s.pyc' % imp.get_tag(), - 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] - self.assertEqual(contents, sorted(wanted)) - - 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 deleted file mode 100644 index 86754a8ce52c..000000000000 --- a/Lib/packaging/tests/test_command_bdist_msi.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Tests for distutils.command.bdist_msi.""" -import sys - -from packaging.tests import unittest, support - - -@unittest.skipUnless(sys.platform == 'win32', 'these tests require Windows') -class BDistMSITestCase(support.TempdirManager, - support.LoggingCatcher, - unittest.TestCase): - - def test_minimal(self): - # minimal test XXX need more tests - from packaging.command.bdist_msi import bdist_msi - project_dir, 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 deleted file mode 100644 index 09bdaadfc903..000000000000 --- a/Lib/packaging/tests/test_command_bdist_wininst.py +++ /dev/null @@ -1,32 +0,0 @@ -"""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 deleted file mode 100644 index 280d709d8d05..000000000000 --- a/Lib/packaging/tests/test_command_build.py +++ /dev/null @@ -1,56 +0,0 @@ -"""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 - pyversion = '%s.%s' % sys.version_info[:2] - plat_spec = '.%s-%s' % (cmd.plat_name, pyversion) - 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-' + pyversion) - 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 deleted file mode 100644 index a2a8583b0fe0..000000000000 --- a/Lib/packaging/tests/test_command_build_clib.py +++ /dev/null @@ -1,141 +0,0 @@ -"""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 deleted file mode 100644 index 9a00c116a577..000000000000 --- a/Lib/packaging/tests/test_command_build_ext.py +++ /dev/null @@ -1,394 +0,0 @@ -import os -import sys -import site -import sysconfig -import textwrap -from packaging.dist import Distribution -from packaging.errors import (UnknownFileError, CompileError, - PackagingPlatformError) -from packaging.command.build_ext import build_ext -from packaging.compiler.extension import Extension - -from test.script_helper import assert_python_ok -from packaging.tests import support, unittest - - -class BuildExtTestCase(support.TempdirManager, - support.LoggingCatcher, - unittest.TestCase): - def setUp(self): - super(BuildExtTestCase, self).setUp() - self.tmp_dir = self.mkdtemp() - self.old_user_base = site.USER_BASE - site.USER_BASE = self.mkdtemp() - - def tearDown(self): - site.USER_BASE = self.old_user_base - super(BuildExtTestCase, self).tearDown() - - def test_build_ext(self): - support.copy_xxmodule_c(self.tmp_dir) - 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) - support.fixup_build_ext(cmd) - cmd.build_lib = self.tmp_dir - cmd.build_temp = self.tmp_dir - cmd.ensure_finalized() - cmd.run() - - code = textwrap.dedent("""\ - import sys - sys.path.insert(0, %r) - - import xx - - for attr in ('error', 'foo', 'new', 'roj'): - assert hasattr(xx, attr) - - assert xx.foo(2, 5) == 7 - assert xx.foo(13, 15) == 28 - assert xx.new().demo() is None - doc = 'This is a template module just for instruction.' - assert xx.__doc__ == doc - assert isinstance(xx.Null(), xx.Null) - assert isinstance(xx.Str(), xx.Str) - """) - code = code % self.tmp_dir - assert_python_ok('-c', code) - - def test_solaris_enable_shared(self): - dist = Distribution({'name': 'xx'}) - cmd = build_ext(dist) - old = sys.platform - - sys.platform = 'sunos' # fooling finalize_options - - old_var = sysconfig.get_config_var('Py_ENABLE_SHARED') - sysconfig._CONFIG_VARS['Py_ENABLE_SHARED'] = 1 - try: - cmd.ensure_finalized() - finally: - sys.platform = old - if old_var is None: - del sysconfig._CONFIG_VARS['Py_ENABLE_SHARED'] - else: - sysconfig._CONFIG_VARS['Py_ENABLE_SHARED'] = old_var - - # make sure we get some library dirs under solaris - self.assertGreater(len(cmd.library_dirs), 0) - - 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, other_lib lastlib' - cmd.finalize_options() - self.assertEqual(cmd.libraries, ['my_lib', 'other_lib', 'lastlib']) - - # 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%sother_lib_dir' % os.pathsep - cmd.finalize_options() - self.assertIn('my_lib_dir', cmd.library_dirs) - self.assertIn('other_lib_dir', cmd.library_dirs) - - # make sure rpath is turned into a list - # if it's a string - cmd = build_ext(dist) - cmd.rpath = 'one%stwo' % os.pathsep - 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 PyInit_foo(void) {}\n') - ext = Extension('foo', [c_file], optional=False) - dist = Distribution({'name': 'xx', - 'ext_modules': [ext]}) - cmd = build_ext(dist) - support.fixup_build_ext(cmd) - cmd.ensure_finalized() - self.assertEqual(len(cmd.get_outputs()), 1) - - 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) - - @unittest.skipUnless(sys.platform == 'darwin', - 'test only relevant for Mac OS X') - def test_deployment_target_default(self): - # Issue 9516: Test that, in the absence of the environment variable, - # an extension module is compiled with the same deployment target as - # the interpreter. - self._try_compile_deployment_target('==', None) - - @unittest.skipUnless(sys.platform == 'darwin', - 'test only relevant for Mac OS X') - def test_deployment_target_too_low(self): - # Issue 9516: Test that an extension module is not allowed to be - # compiled with a deployment target less than that of the interpreter. - self.assertRaises(PackagingPlatformError, - self._try_compile_deployment_target, '>', '10.1') - - @unittest.skipUnless(sys.platform == 'darwin', - 'test only relevant for Mac OS X') - def test_deployment_target_higher_ok(self): - # Issue 9516: Test that an extension module can be compiled with a - # deployment target higher than that of the interpreter: the ext - # module may depend on some newer OS feature. - deptarget = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') - if deptarget: - # increment the minor version number (i.e. 10.6 -> 10.7) - deptarget = [int(x) for x in deptarget.split('.')] - deptarget[-1] += 1 - deptarget = '.'.join(str(i) for i in deptarget) - self._try_compile_deployment_target('<', deptarget) - - def _try_compile_deployment_target(self, operator, target): - orig_environ = os.environ - os.environ = orig_environ.copy() - self.addCleanup(setattr, os, 'environ', orig_environ) - - if target is None: - if os.environ.get('MACOSX_DEPLOYMENT_TARGET'): - del os.environ['MACOSX_DEPLOYMENT_TARGET'] - else: - os.environ['MACOSX_DEPLOYMENT_TARGET'] = target - - deptarget_c = os.path.join(self.tmp_dir, 'deptargetmodule.c') - - with open(deptarget_c, 'w') as fp: - fp.write(textwrap.dedent('''\ - #include - - int dummy; - - #if TARGET %s MAC_OS_X_VERSION_MIN_REQUIRED - #else - #error "Unexpected target" - #endif - - ''' % operator)) - - # get the deployment target that the interpreter was built with - target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') - target = tuple(map(int, target.split('.'))) - target = '%02d%01d0' % target - - deptarget_ext = Extension( - 'deptarget', - [deptarget_c], - extra_compile_args=['-DTARGET=%s' % (target,)], - ) - dist = Distribution({ - 'name': 'deptarget', - 'ext_modules': [deptarget_ext], - }) - dist.package_dir = self.tmp_dir - cmd = build_ext(dist) - cmd.build_lib = self.tmp_dir - cmd.build_temp = self.tmp_dir - - try: - cmd.ensure_finalized() - cmd.run() - except CompileError: - self.fail("Wrong deployment target during compilation") - - -def test_suite(): - 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 deleted file mode 100644 index 0599bf23bf90..000000000000 --- a/Lib/packaging/tests/test_command_build_py.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Tests for distutils.command.build_py.""" - -import os -import sys -import imp - -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() - # let's have two files to make sure globbing works - f = open(os.path.join(pkg_dir, "README.txt"), "w") - try: - f.write("Info about this package") - finally: - f.close() - f = open(os.path.join(pkg_dir, "HACKING.txt"), "w") - try: - f.write("How to contribute") - finally: - f.close() - - destination = self.mkdtemp() - - dist = Distribution({"packages": ["pkg"], - "package_dir": sources}) - - 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": ["*.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!). - # FIXME the test below is not doing what the comment above says, and - # if it did it would show a code bug: if we add a demo.py file to - # package_data, it gets byte-compiled! - outputs = cmd.get_outputs() - self.assertEqual(len(outputs), 4, outputs) - pkgdest = os.path.join(destination, "pkg") - files = os.listdir(pkgdest) - pycache_dir = os.path.join(pkgdest, "__pycache__") - self.assertIn("__init__.py", files) - self.assertIn("README.txt", files) - self.assertIn("HACKING.txt", files) - pyc_files = os.listdir(pycache_dir) - self.assertEqual(["__init__.%s.pyc" % imp.get_tag()], pyc_files) - - def test_empty_package_dir(self): - # See SF 1668596/1720897. - # create the distribution files. - sources = self.mkdtemp() - pkg = os.path.join(sources, 'pkg') - os.mkdir(pkg) - open(os.path.join(pkg, "__init__.py"), "wb").close() - testdir = os.path.join(pkg, "doc") - os.mkdir(testdir) - open(os.path.join(testdir, "testfile"), "wb").close() - - os.chdir(sources) - dist = Distribution({"packages": ["pkg"], - "package_dir": sources, - "package_data": {"pkg": ["doc/*"]}}) - dist.script_args = ["build"] - dist.parse_command_line() - - try: - dist.run_commands() - except PackagingFileError: - self.fail("failed package_data test when package_dir is ''") - - def test_byte_compile(self): - project_dir, dist = self.create_dist(py_modules=['boiledeggs']) - os.chdir(project_dir) - self.write_file('boiledeggs.py', 'import antigravity') - cmd = build_py(dist) - cmd.compile = True - cmd.build_lib = 'here' - cmd.finalize_options() - cmd.run() - - found = os.listdir(cmd.build_lib) - self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) - found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) - self.assertEqual(found, ['boiledeggs.%s.pyc' % imp.get_tag()]) - - def test_byte_compile_optimized(self): - project_dir, dist = self.create_dist(py_modules=['boiledeggs']) - os.chdir(project_dir) - self.write_file('boiledeggs.py', 'import antigravity') - cmd = build_py(dist) - cmd.compile = True - cmd.optimize = 1 - cmd.build_lib = 'here' - cmd.finalize_options() - cmd.run() - - found = os.listdir(cmd.build_lib) - self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) - found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) - self.assertEqual(sorted(found), ['boiledeggs.%s.pyc' % imp.get_tag(), - 'boiledeggs.%s.pyo' % imp.get_tag()]) - - def test_byte_compile_under_B(self): - # make sure byte compilation works under -B (dont_write_bytecode) - self.addCleanup(setattr, sys, 'dont_write_bytecode', - sys.dont_write_bytecode) - sys.dont_write_bytecode = True - self.test_byte_compile() - self.test_byte_compile_optimized() - - -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 deleted file mode 100644 index fd3ac246a633..000000000000 --- a/Lib/packaging/tests/test_command_build_scripts.py +++ /dev/null @@ -1,109 +0,0 @@ -"""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): - with open(os.path.join(dir, name), "w") as f: - f.write(text) - - 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 deleted file mode 100644 index 0b9105057035..000000000000 --- a/Lib/packaging/tests/test_command_check.py +++ /dev/null @@ -1,161 +0,0 @@ -"""Tests for distutils.command.check.""" - -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 - 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(), []) - - # now let's add the required fields - # and run it again, to make sure we don't get - # any warning anymore - metadata = {'home_page': 'xxx', 'author': 'xxx', - 'author_email': 'xxx', - 'name': 'xxx', 'version': '4.2', - } - self._run(metadata) - self.assertEqual(self.get_logs(), []) - - # 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}) - - # clear warnings from the previous calls - self.loghandler.flush() - - # and of course, no error when all metadata fields are present - self._run(metadata, strict=True) - self.assertEqual(self.get_logs(), []) - - # now a test with non-ASCII characters - metadata = {'home_page': 'xxx', 'author': '\u00c9ric', - 'author_email': 'xxx', 'name': 'xxx', - 'version': '1.2', - 'summary': 'Something about esszet \u00df', - 'description': 'More things about esszet \u00df'} - self._run(metadata) - self.assertEqual(self.get_logs(), []) - - 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 - self._run() - self.assertNotEqual(self.get_logs(), []) - - # 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._run(metadata) - self.assertEqual(self.get_logs(), []) - - # 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}) - - # clear warnings from the previous calls - self.loghandler.flush() - - # now with correct version format again - metadata['version'] = '4.2' - self._run(metadata, strict=True) - self.assertEqual(self.get_logs(), []) - - @unittest.skipUnless(_HAS_DOCUTILS, "requires docutils") - def test_check_restructuredtext(self): - # let's see if it detects broken rest in 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()), 1) - - # let's see if we have an error with strict=1 - metadata = {'home_page': 'xxx', 'author': 'xxx', - 'author_email': 'xxx', - 'name': 'xxx', 'version': '1.2', - 'description': broken_rest} - self.assertRaises(PackagingSetupError, self._run, metadata, - strict=True, all=True) - self.loghandler.flush() - - # and non-broken rest, including a non-ASCII character to test #12114 - dist = self.create_dist(description='title\n=====\n\ntest \u00df')[1] - cmd = check(dist) - cmd.check_restructuredtext() - self.assertEqual(self.get_logs(), []) - - 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()), 1) - - def test_warn(self): - _, dist = self.create_dist() - cmd = check(dist) - self.assertEqual(self.get_logs(), []) - cmd.warn('hello') - self.assertEqual(self.get_logs(), ['check: hello']) - cmd.warn('hello %s', 'world') - self.assertEqual(self.get_logs(), ['check: hello world']) - cmd.warn('hello %s %s', 'beautiful', 'world') - self.assertEqual(self.get_logs(), ['check: hello beautiful world']) - - -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 deleted file mode 100644 index a78c3a7a37df..000000000000 --- a/Lib/packaging/tests/test_command_clean.py +++ /dev/null @@ -1,46 +0,0 @@ -"""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((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.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 deleted file mode 100644 index 6d00ec37374f..000000000000 --- a/Lib/packaging/tests/test_command_cmd.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for distutils.cmd.""" -import os -import logging - -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(logging.INFO) - 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 deleted file mode 100644 index dae75b4f87aa..000000000000 --- a/Lib/packaging/tests/test_command_config.py +++ /dev/null @@ -1,76 +0,0 @@ -"""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 deleted file mode 100644 index 8d4373d685de..000000000000 --- a/Lib/packaging/tests/test_command_install_data.py +++ /dev/null @@ -1,148 +0,0 @@ -"""Tests for packaging.command.install_data.""" -import os -import sys -import sysconfig -import packaging.database -from sysconfig import _get_default_scheme -from packaging.tests import unittest, support -from packaging.command.install_data import install_data -from packaging.command.install_dist import install_dist -from packaging.command.install_distinfo import install_distinfo - - -class InstallDataTestCase(support.TempdirManager, - support.LoggingCatcher, - unittest.TestCase): - - def setUp(self): - super(InstallDataTestCase, self).setUp() - scheme = _get_default_scheme() - old_items = sysconfig._SCHEMES.items(scheme) - - def restore(): - sysconfig._SCHEMES.remove_section(scheme) - sysconfig._SCHEMES.add_section(scheme) - for option, value in old_items: - sysconfig._SCHEMES.set(scheme, option, value) - - self.addCleanup(restore) - - def test_simple_run(self): - pkg_dir, dist = self.create_dist() - cmd = install_data(dist) - cmd.install_dir = inst = os.path.join(pkg_dir, 'inst') - scheme = _get_default_scheme() - - sysconfig._SCHEMES.set(scheme, 'inst', - os.path.join(pkg_dir, 'inst')) - sysconfig._SCHEMES.set(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') - - # FIXME this creates a literal \{inst2\} directory! - 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.finalized = False - 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(scheme, 'inst3', cmd.install_dir) - - cmd.data_files = {one: '{inst}/one', two: '{inst2}/two', - three: '{inst3}/three'} - cmd.finalized = False - 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_resources(self): - install_dir = self.mkdtemp() - scripts_dir = self.mkdtemp() - project_dir, dist = self.create_dist( - name='Spamlib', version='0.1', - data_files={'spamd': '{scripts}/spamd'}) - - os.chdir(project_dir) - self.write_file('spamd', '# Python script') - sysconfig._SCHEMES.set(_get_default_scheme(), 'scripts', scripts_dir) - sys.path.insert(0, install_dir) - packaging.database.disable_cache() - self.addCleanup(sys.path.remove, install_dir) - self.addCleanup(packaging.database.enable_cache) - - cmd = install_dist(dist) - cmd.outputs = ['spamd'] - cmd.install_lib = install_dir - dist.command_obj['install_dist'] = cmd - - cmd = install_data(dist) - cmd.install_dir = install_dir - cmd.ensure_finalized() - dist.command_obj['install_data'] = cmd - cmd.run() - - cmd = install_distinfo(dist) - cmd.ensure_finalized() - dist.command_obj['install_distinfo'] = cmd - cmd.run() - - # first a few sanity checks - self.assertEqual(os.listdir(scripts_dir), ['spamd']) - self.assertEqual(os.listdir(install_dir), ['Spamlib-0.1.dist-info']) - - # now the real test - fn = os.path.join(install_dir, 'Spamlib-0.1.dist-info', 'RESOURCES') - with open(fn, encoding='utf-8') as fp: - content = fp.read().strip() - - expected = 'spamd,%s' % os.path.join(scripts_dir, 'spamd') - self.assertEqual(content, expected) - - # just to be sure, we also test that get_file works here, even though - # packaging.database has its own test file - with packaging.database.get_file('Spamlib', 'spamd') as fp: - content = fp.read() - - self.assertEqual('# Python script', content) - - -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 deleted file mode 100644 index 3345d2e39afb..000000000000 --- a/Lib/packaging/tests/test_command_install_dist.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Tests for packaging.command.install.""" - -import os -import imp -import sys -from sysconfig import (get_scheme_names, get_config_vars, - _SCHEMES, get_config_var, get_path) - -from packaging.command.build_ext import build_ext -from packaging.command.install_dist import install_dist -from packaging.compiler.extension import Extension -from packaging.dist import Distribution -from packaging.errors import PackagingOptionError - -from packaging.tests import unittest, support - - -_CONFIG_VARS = get_config_vars() - - -def _make_ext_name(modname): - if os.name == 'nt' and sys.executable.endswith('_d.exe'): - modname += '_d' - return modname + get_config_var('SO') - - -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"}) - 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) - - 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 - - def cleanup(): - _CONFIG_VARS['userbase'] = self.old_user_base - _SCHEMES.set(scheme, 'purelib', self.old_user_site) - os.path.expanduser = self.old_expand - - self.addCleanup(cleanup) - - 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) - - # 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() - project_dir, dist = self.create_dist(py_modules=['hello'], - scripts=['sayhi']) - os.chdir(project_dir) - self.write_file('hello.py', "def main(): print('o hai')") - self.write_file('sayhi', 'from hello import main; main()') - - cmd = install_dist(dist) - dist.command_obj['install_dist'] = cmd - cmd.root = install_dir - cmd.record = os.path.join(project_dir, 'filelist') - cmd.ensure_finalized() - cmd.run() - - with open(cmd.record) as f: - content = f.read() - - found = [os.path.basename(line) for line in content.splitlines()] - expected = ['hello.py', 'hello.%s.pyc' % imp.get_tag(), 'sayhi', - 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] - self.assertEqual(sorted(found), sorted(expected)) - - # XXX test that fancy_getopt is okay with options named - # record and no-record but unrelated - - def test_old_record_extensions(self): - # test pre-PEP 376 --record option with ext modules - install_dir = self.mkdtemp() - project_dir, dist = self.create_dist(ext_modules=[ - Extension('xx', ['xxmodule.c'])]) - os.chdir(project_dir) - support.copy_xxmodule_c(project_dir) - - buildextcmd = build_ext(dist) - support.fixup_build_ext(buildextcmd) - buildextcmd.ensure_finalized() - - cmd = install_dist(dist) - dist.command_obj['install_dist'] = cmd - dist.command_obj['build_ext'] = buildextcmd - cmd.root = install_dir - cmd.record = os.path.join(project_dir, 'filelist') - cmd.ensure_finalized() - cmd.run() - - with open(cmd.record) as f: - content = f.read() - - found = [os.path.basename(line) for line in content.splitlines()] - expected = [_make_ext_name('xx'), - 'METADATA', 'INSTALLER', 'REQUESTED', 'RECORD'] - self.assertEqual(found, expected) - - -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 deleted file mode 100644 index 33153e7fe3ad..000000000000 --- a/Lib/packaging/tests/test_command_install_distinfo.py +++ /dev/null @@ -1,252 +0,0 @@ -"""Tests for ``packaging.command.install_distinfo``. - -Writing of the RESOURCES file is tested in test_command_install_data. -""" - -import os -import csv -import hashlib -import sysconfig - -from packaging.command.install_distinfo import install_distinfo -from packaging.command.cmd import Command -from packaging.compiler.extension import Extension -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.install_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.install_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.install_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.install_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_basic(self): - install_dir = self.mkdtemp() - modules_dest = os.path.join(install_dir, 'lib') - scripts_dest = os.path.join(install_dir, 'bin') - project_dir, dist = self.create_dist( - name='Spamlib', version='0.1', - py_modules=['spam'], scripts=['spamd'], - ext_modules=[Extension('_speedspam', ['_speedspam.c'])]) - - # using a real install_dist command is too painful, so we use a mock - # class that's only a holder for options to be used by install_distinfo - # and we create placeholder files manually instead of using build_*. - # the install_* commands will still be consulted by install_distinfo. - os.chdir(project_dir) - self.write_file('spam', '# Python module') - self.write_file('spamd', '# Python script') - extmod = '_speedspam' + sysconfig.get_config_var('SO') - self.write_file(extmod, '') - - install = DummyInstallCmd(dist) - install.outputs = ['spam', 'spamd', extmod] - install.install_lib = modules_dest - install.install_scripts = scripts_dest - dist.command_obj['install_dist'] = install - - cmd = install_distinfo(dist) - cmd.ensure_finalized() - dist.command_obj['install_distinfo'] = cmd - cmd.run() - - # checksum and size are not hard-coded for METADATA as it is - # platform-dependent (line endings) - metadata = os.path.join(modules_dest, 'Spamlib-0.1.dist-info', - 'METADATA') - with open(metadata, 'rb') as fp: - content = fp.read() - - metadata_size = str(len(content)) - metadata_md5 = hashlib.md5(content).hexdigest() - - record = os.path.join(modules_dest, 'Spamlib-0.1.dist-info', 'RECORD') - with open(record, encoding='utf-8') as fp: - content = fp.read() - - found = [] - for line in content.splitlines(): - filename, checksum, size = line.split(',') - filename = os.path.basename(filename) - found.append((filename, checksum, size)) - - expected = [ - ('spam', '6ab2f288ef2545868effe68757448b45', '15'), - ('spamd', 'd13e6156ce78919a981e424b2fdcd974', '15'), - (extmod, 'd41d8cd98f00b204e9800998ecf8427e', '0'), - ('METADATA', metadata_md5, metadata_size), - ('INSTALLER', '44e3fde05f3f537ed85831969acf396d', '9'), - ('REQUESTED', 'd41d8cd98f00b204e9800998ecf8427e', '0'), - ('RECORD', '', ''), - ] - self.assertEqual(found, expected) - - 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.install_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', '.pyo')) 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, 'rb') as fp: - md5.update(fp.read()) - 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 deleted file mode 100644 index f2906a7e1802..000000000000 --- a/Lib/packaging/tests/test_command_install_headers.py +++ /dev/null @@ -1,38 +0,0 @@ -"""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 deleted file mode 100644 index 79e8fa8196bb..000000000000 --- a/Lib/packaging/tests/test_command_install_lib.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Tests for packaging.command.install_data.""" -import os -import sys -import imp - -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 - - -class InstallLibTestCase(support.TempdirManager, - support.LoggingCatcher, - support.EnvironRestorer, - unittest.TestCase): - - restore_environ = ['PYTHONPATH'] - - def test_finalize_options(self): - dist = self.create_dist()[1] - 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) - - def test_byte_compile(self): - project_dir, dist = self.create_dist() - os.chdir(project_dir) - cmd = install_lib(dist) - cmd.compile = True - cmd.optimize = 1 - - f = os.path.join(project_dir, 'foo.py') - self.write_file(f, '# python file') - cmd.byte_compile([f]) - pyc_file = imp.cache_from_source('foo.py', True) - pyo_file = imp.cache_from_source('foo.py', False) - self.assertTrue(os.path.exists(pyc_file)) - self.assertTrue(os.path.exists(pyo_file)) - - def test_byte_compile_under_B(self): - # make sure byte compilation works under -B (dont_write_bytecode) - self.addCleanup(setattr, sys, 'dont_write_bytecode', - sys.dont_write_bytecode) - sys.dont_write_bytecode = True - self.test_byte_compile() - - def test_get_outputs(self): - project_dir, dist = self.create_dist() - os.chdir(project_dir) - os.mkdir('spam') - cmd = install_lib(dist) - - # setting up a dist environment - cmd.compile = True - cmd.optimize = 1 - cmd.install_dir = self.mkdtemp() - f = os.path.join(project_dir, 'spam', '__init__.py') - self.write_file(f, '# python package') - cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] - cmd.distribution.packages = ['spam'] - - # make sure the build_lib is set the temp dir # XXX what? this is not - # needed in the same distutils test and should work without manual - # intervention - build_dir = os.path.split(project_dir)[0] - cmd.get_finalized_command('build_py').build_lib = build_dir - - # get_outputs should return 4 elements: spam/__init__.py, .pyc and - # .pyo, foo.import-tag-abiflags.so / foo.pyd - outputs = cmd.get_outputs() - self.assertEqual(len(outputs), 4, outputs) - - def test_get_inputs(self): - project_dir, dist = self.create_dist() - os.chdir(project_dir) - os.mkdir('spam') - cmd = install_lib(dist) - - # setting up a dist environment - cmd.compile = True - cmd.optimize = 1 - cmd.install_dir = self.mkdtemp() - f = os.path.join(project_dir, 'spam', '__init__.py') - self.write_file(f, '# python package') - cmd.distribution.ext_modules = [Extension('foo', ['xxx'])] - cmd.distribution.packages = ['spam'] - - # get_inputs should return 2 elements: spam/__init__.py and - # foo.import-tag-abiflags.so / foo.pyd - inputs = cmd.get_inputs() - self.assertEqual(len(inputs), 2, inputs) - - -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 deleted file mode 100644 index 6452a3455ae3..000000000000 --- a/Lib/packaging/tests/test_command_install_scripts.py +++ /dev/null @@ -1,75 +0,0 @@ -"""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) - with open(os.path.join(source, name), "w") as f: - f.write(text) - - 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 deleted file mode 100644 index 07fad8998ebd..000000000000 --- a/Lib/packaging/tests/test_command_register.py +++ /dev/null @@ -1,260 +0,0 @@ -"""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.tests.support import Inputs -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 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 = {'home_page': '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.finalized = False - 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(b'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'], '628') - self.assertIn(b'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'], '298') - self.assertIn(b'tarek', req.data) - - @unittest.skipUnless(DOCUTILS_SUPPORT, 'needs docutils') - def test_strict(self): - # testing the strict option: when on, the register command stops if the - # metadata is incomplete or if description contains bad reST - - # empty metadata # XXX this is not really empty.. - 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 description is broken - metadata = {'home_page': 'xxx', 'author': 'xxx', - 'author_email': 'éxéxé', - 'name': 'xxx', 'version': '4.2', - '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() - - # and finally a Unicode test (bug #12114) - metadata = {'home_page': 'xxx', 'author': '\u00c9ric', - 'author_email': 'xxx', 'name': 'xxx', - 'version': 'xxx', - 'summary': 'Something about esszet \u00df', - 'description': 'More things about esszet \u00df'} - - 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() - - 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 deleted file mode 100644 index d9747189d1d5..000000000000 --- a/Lib/packaging/tests/test_command_sdist.py +++ /dev/null @@ -1,394 +0,0 @@ -"""Tests for packaging.command.sdist.""" -import os -import tarfile -import zipfile - -try: - import grp - import pwd - UID_GID_SUPPORT = True -except ImportError: - UID_GID_SUPPORT = False - -from shutil import get_archive_formats -from os.path import join -from packaging.dist import Distribution -from packaging.util import find_executable -from packaging.errors import PackagingOptionError -from packaging.command.sdist import sdist, show_formats - -from test.support import captured_stdout -from packaging.tests import support, unittest -from packaging.tests.support import requires_zlib - - -MANIFEST = """\ -# file GENERATED by packaging, do NOT edit -inroot.txt -setup.cfg -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): - 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'), '#') - 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', - 'home_page': 'xxx', 'author': 'xxx', - 'author_email': 'xxx'} - dist = Distribution(metadata) - dist.packages = ['somecode'] - cmd = sdist(dist) - cmd.dist_dir = 'dist' - return dist, cmd - - @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), 2) - - @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.finalized = False - cmd.ensure_finalized() - cmd.run() - - result = sorted(os.listdir(dist_folder)) - self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) - - @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, 'setup.cfg'), '#') - 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 8 code and data - # files in addition to PKG-INFO and setup.cfg - 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}) - - @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 cause the check subcommand to log two warnings: - # version is invalid, home-page and author are missing - cmd.ensure_finalized() - cmd.run() - warnings = self.get_logs() - check_warnings = [msg for msg in warnings if - not msg.startswith('sdist:')] - self.assertEqual(len(check_warnings), 2, warnings) - - # 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() - self.assertEqual(len(warnings), 2) - self.assertIn('using default file list', warnings[0]) - self.assertIn("'setup.cfg' file not found", warnings[1]) - - def test_show_formats(self): - with captured_stdout() as stdout: - show_formats() - stdout = stdout.getvalue() - - # 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) - - @requires_zlib - 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) - - @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()) - - @requires_zlib - 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), 3) - - # 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), 4) - self.assertIn('doc2.txt', manifest2[-1]) - - @requires_zlib - 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') - - @requires_zlib - 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']) - - @requires_zlib - 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 deleted file mode 100644 index 7aa1f7991074..000000000000 --- a/Lib/packaging/tests/test_command_test.py +++ /dev/null @@ -1,224 +0,0 @@ -import os -import re -import sys -import shutil -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) - - @unittest.skip('needs to be written') - 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() - self.assertIn(phony_project, logs[-1]) - - 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 deleted file mode 100644 index 1f68c1d96b7a..000000000000 --- a/Lib/packaging/tests/test_command_upload.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Tests for packaging.command.upload.""" -import os - -from packaging.command.upload import upload -from packaging.dist import Distribution -from packaging.errors import PackagingOptionError - -from packaging.tests import unittest, support -try: - import threading - from packaging.tests.pypi_server import PyPIServerTestCase -except ImportError: - threading = None - PyPIServerTestCase = unittest.TestCase - - -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/ -""" - - -@unittest.skipIf(threading is None, 'needs threading') -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)] - - # let's run it - dist = self.create_dist(dist_files=dist_files, author='dédé')[1] - 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é'.encode('utf-8'), 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((docs_path, "index.html"), "yellow") - self.write_file(self.rc, PYPIRC) - - # let's run it - dist = self.create_dist(dist_files=dist_files, author='dédé')[1] - - cmd = upload(dist) - cmd.get_finalized_command("build").run() - cmd.upload_docs = True - cmd.ensure_finalized() - cmd.repository = self.pypi.full_address - os.chdir(self.tmp_dir) - cmd.run() - - 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 deleted file mode 100644 index 803e733d3029..000000000000 --- a/Lib/packaging/tests/test_command_upload_docs.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Tests for packaging.command.upload_docs.""" -import os -import shutil -import logging -import zipfile -try: - import _ssl -except ImportError: - _ssl = None - -from packaging.command import upload_docs as upload_docs_mod -from packaging.command.upload_docs import upload_docs, zip_dir -from packaging.dist import Distribution -from packaging.errors import PackagingFileError, PackagingOptionError - -from packaging.tests import unittest, support -try: - import threading - from packaging.tests.pypi_server import PyPIServerTestCase -except ImportError: - threading = None - PyPIServerTestCase = unittest.TestCase - - -PYPIRC = """\ -[distutils] -index-servers = server1 - -[server1] -repository = %s -username = real_slim_shady -password = long_island -""" - - -@unittest.skipIf(threading is None, "Needs threading") -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() - os.chdir(sandbox) - os.mkdir("build") - self.prepare_sample_dir("build") - self.cmd.ensure_finalized() - self.assertEqual(self.cmd.upload_dir, os.path.join("build", "docs")) - - def test_default_uploaddir_looks_for_doc_also(self): - sandbox = self.mkdtemp() - os.chdir(sandbox) - 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")) - - 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((sample_dir, "docs", "index.html"), "Ce mortel ennui") - self.write_file((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 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( - b'----------------GHSKFJDLGDS7543FJKLFHRE75642756743254')[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) - - @unittest.skipIf(_ssl is None, 'Needs SSL support') - def test_https_connection(self): - self.https_called = False - self.addCleanup( - setattr, upload_docs_mod.http.client, 'HTTPSConnection', - upload_docs_mod.http.client.HTTPSConnection) - - def https_conn_wrapper(*args): - self.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 - - self.prepare_command() - self.cmd.run() - self.assertFalse(self.https_called) - - self.cmd.repository = self.cmd.repository.replace("http", "https") - self.cmd.run() - self.assertTrue(self.https_called) - - def test_handling_response(self): - self.pypi.default_response_status = '403 Forbidden' - self.prepare_command() - self.cmd.run() - errors = self.get_logs(logging.ERROR) - self.assertEqual(len(errors), 1) - self.assertIn('Upload failed (403): Forbidden', errors[0]) - - self.pypi.default_response_status = '301 Moved Permanently' - self.pypi.default_response_headers.append( - ("Location", "brand_new_location")) - self.cmd.run() - lastlog = self.get_logs(logging.INFO)[-1] - self.assertIn('brand_new_location', lastlog) - - 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(logging.INFO)[-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 deleted file mode 100644 index 2c620cb513ad..000000000000 --- a/Lib/packaging/tests/test_compiler.py +++ /dev/null @@ -1,66 +0,0 @@ -"""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 deleted file mode 100644 index 0d76b29a0464..000000000000 --- a/Lib/packaging/tests/test_config.py +++ /dev/null @@ -1,519 +0,0 @@ -"""Tests for packaging.config.""" -import os -import sys - -from packaging import command -from packaging.dist import Distribution -from packaging.errors import PackagingFileError, PackagingOptionError -from packaging.compiler import new_compiler, _COMPILERS -from packaging.command.sdist import sdist - -from packaging.tests import unittest, support -from packaging.tests.support import requires_zlib - - -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/* doc/* - doc/images/*.png - - -extra_files = %(extra-files)s - -# Replaces MANIFEST.in -# FIXME no, it's extra_files -# (but sdist_extra is a better name, should use it) -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_hooks = %(setup-hooks)s - - - -[install_dist] -sub_commands = foo -""" - -SETUP_CFG_PKGDATA_BUGGY_1 = """ -[files] -package_data = foo.* -""" - -SETUP_CFG_PKGDATA_BUGGY_2 = """ -[files] -package_data = - foo.* -""" - -# Can not be merged with SETUP_CFG else install_dist -# command will fail when trying to compile C sources -# TODO use a DummyCommand to mock build_ext -EXT_SETUP_CFG = """ -[files] -packages = one - two - parent.undeclared - -[extension: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: two.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 - -# corner case: if the parent package of an extension is declared but -# not its grandparent, it's legal -[extension: parent.undeclared._speed] -sources = parent/undeclared/_speed.c -""" - -EXT_SETUP_CFG_BUGGY_1 = """ -[extension: realname] -name = crash_here -""" - -EXT_SETUP_CFG_BUGGY_2 = """ -[files] -packages = ham - -[extension: spam.eggs] -""" - -EXT_SETUP_CFG_BUGGY_3 = """ -[files] -packages = ok - ok.works - -[extension: ok.works.breaks._ext] -""" - -HOOKS_MODULE = """ -import logging - -logger = logging.getLogger('packaging') - -def logging_hook(config): - logger.warning('logging_hook called') -""" - - -class DCompiler: - name = 'd' - description = 'D Compiler' - - def __init__(self, *args): - pass - - -def version_hook(config): - config['metadata']['version'] += '.dev1' - - -def first_hook(config): - config['files']['modules'] += '\n first' - - -def third_hook(config): - config['files']['modules'] += '\n third' - - -class FooBarBazTest: - - def __init__(self, dist): - self.distribution = dist - self._record = [] - - @classmethod - def get_command_name(cls): - return 'foo' - - def run(self): - self._record.append('foo has run') - - 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() - tempdir = self.mkdtemp() - self.working_dir = os.getcwd() - os.chdir(tempdir) - self.tempdir = tempdir - - def write_setup(self, kwargs=None): - opts = {'description-file': 'README', 'extra-files': '', - 'setup-hooks': 'packaging.tests.test_config.version_hook'} - if kwargs: - opts.update(kwargs) - self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8') - - 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/*', 'doc/*', - 'doc/images/*.png']}) - self.assertEqual(dist.data_files, - {'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'}) - - 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(dist.get_command_obj('foo').__class__.__name__, - 'FooBarBazTest') - - # 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') - - # check error reporting for invalid package_data value - self.write_file('setup.cfg', SETUP_CFG_PKGDATA_BUGGY_1) - self.assertRaises(PackagingOptionError, self.get_dist) - - self.write_file('setup.cfg', SETUP_CFG_PKGDATA_BUGGY_2) - self.assertRaises(PackagingOptionError, self.get_dist) - - 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), 3) - 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('two.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') - - self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_1) - self.assertRaises(PackagingOptionError, self.get_dist) - - self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_2) - self.assertRaises(PackagingOptionError, self.get_dist) - - self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_3) - self.assertRaises(PackagingOptionError, self.get_dist) - - def test_project_setup_hook_works(self): - # Bug #11637: ensure the project directory is on sys.path to allow - # project-specific hooks - self.write_setup({'setup-hooks': 'hooks.logging_hook'}) - self.write_file('README', 'yeah') - self.write_file('hooks.py', HOOKS_MODULE) - self.get_dist() - self.assertEqual(['logging_hook called'], self.get_logs()) - self.assertIn('hooks', sys.modules) - - def test_missing_setup_hook_warns(self): - self.write_setup({'setup-hooks': 'does._not.exist'}) - self.write_file('README', 'yeah') - self.get_dist() - logs = self.get_logs() - self.assertEqual(1, len(logs)) - self.assertIn('cannot find setup hook', logs[0]) - - def test_multiple_setup_hooks(self): - self.write_setup({ - 'setup-hooks': '\n packaging.tests.test_config.first_hook' - '\n packaging.tests.test_config.missing_hook' - '\n packaging.tests.test_config.third_hook', - }) - self.write_file('README', 'yeah') - dist = self.get_dist() - - self.assertEqual(['haven', 'first', 'third'], dist.py_modules) - logs = self.get_logs() - self.assertEqual(1, len(logs)) - self.assertIn('cannot find setup hook', logs[0]) - - def test_metadata_requires_description_files_missing(self): - self.write_setup({'description-file': 'README 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) - - @requires_zlib - 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 - self.addCleanup(command._COMMANDS.__delitem__, 'foo') - dist = self.get_dist() - dist.run_command('install_dist') - cmd = dist.get_command_obj('foo') - self.assertEqual(cmd.__class__.__name__, 'FooBarBazTest') - self.assertEqual(cmd._record, ['foo has run']) - - -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 deleted file mode 100644 index 76bc3316b425..000000000000 --- a/Lib/packaging/tests/test_create.py +++ /dev/null @@ -1,233 +0,0 @@ -"""Tests for packaging.create.""" -import os -import sys -import sysconfig -from textwrap import dedent -from packaging import create -from packaging.create import MainProgram, ask_yn, ask, main - -from packaging.tests import support, unittest -from packaging.tests.support import Inputs - - -class CreateTestCase(support.TempdirManager, - support.EnvironRestorer, - support.LoggingCatcher, - unittest.TestCase): - - maxDiff = None - restore_environ = ['PLAT'] - - def setUp(self): - super(CreateTestCase, self).setUp() - 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): - sysconfig.get_paths = self._old_get_paths - if hasattr(create, 'input'): - del create.input - super(CreateTestCase, self).tearDown() - - def test_ask_yn(self): - create.input = Inputs('y') - self.assertEqual('y', ask_yn('is this a test')) - - def test_ask(self): - create.input = Inputs('a', 'b') - 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() - create.input = Inputs('aaaaa') - 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', - 'data/data1', - 'foo.py', - 'pkg1/__init__.py', - 'pkg1/bar.py', - 'pkg2/__init__.py', - 'pkg2/sub/__init__.py', - ] - - for dir_ in dirs: - os.mkdir(os.path.join(tempdir, dir_)) - - for file_ in files: - self.write_file((tempdir, file_), '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(mainprogram.data['extra_files'], - ['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'], - 'pyxfoil': ['fengine.so'], - }, - scripts=['my_script', 'bin/run'], - ) - """), encoding='utf-8') - create.input = Inputs('y') - main() - - path = os.path.join(self.wdir, 'setup.cfg') - with open(path, encoding='utf-8') as fp: - contents = fp.read() - - self.assertEqual(contents, dedent("""\ - [metadata] - name = pyxfoil - version = 0.2 - summary = Python bindings for the Xfoil engine - download_url = UNKNOWN - home_page = http://www.python-science.org/project/pyxfoil - maintainer = André Espaze - maintainer_email = andre.espaze@logilab.fr - description = My super Death-scription - |barbar is now on the public domain, - |ho, baby ! - - [files] - packages = pyxfoil - babar - me - modules = my_lib - mymodule - scripts = my_script - bin/run - package_data = - babar = Pom - Flora - Alexander - me = dady - mumy - sys - bro - pyxfoil = fengine.so - - 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 - with open('README.txt') as fp: - long_description = fp.read() - - 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']), - ], - ) - """), encoding='utf-8') - self.write_file((self.wdir, 'README.txt'), - dedent(''' -My super Death-scription -barbar is now in the public domain, -ho, baby! - ''')) - create.input = Inputs('y') - main() - - path = os.path.join(self.wdir, 'setup.cfg') - with open(path, encoding='utf-8') as fp: - contents = fp.read() - - self.assertEqual(contents, dedent("""\ - [metadata] - name = pyxfoil - version = 0.2 - summary = Python bindings for the Xfoil engine - download_url = UNKNOWN - home_page = http://www.python-science.org/project/pyxfoil - maintainer = André Espaze - maintainer_email = andre.espaze@logilab.fr - description-file = README.txt - - [files] - packages = pyxfoil - package_data = - pyxfoil = fengine.so - 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 deleted file mode 100644 index 17c43cd28aea..000000000000 --- a/Lib/packaging/tests/test_cygwinccompiler.py +++ /dev/null @@ -1,88 +0,0 @@ -"""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 deleted file mode 100644 index ad91b94ca3d2..000000000000 --- a/Lib/packaging/tests/test_database.py +++ /dev/null @@ -1,686 +0,0 @@ -import os -import io -import csv -import sys -import shutil -import tempfile -from hashlib import md5 -from textwrap import dedent - -from packaging.tests.test_util import GlobTestCaseBase -from packaging.tests.support import requires_zlib - -import packaging.database -from packaging.config import get_resources_dests -from packaging.errors import PackagingError -from packaging.metadata import Metadata -from packaging.tests import unittest, support -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, - get_file, get_file_path) - -# 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(path): - path = os.path.join(*path) - digest = get_hexdigest(path) - size = os.path.getsize(path) - return path, digest, size - - -class FakeDistsMixin: - - def setUp(self): - super(FakeDistsMixin, self).setUp() - self.addCleanup(enable_cache) - disable_cache() - - # make a copy that we can write into for our fake installed - # distributions - tmpdir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, tmpdir) - self.fake_dists_path = os.path.realpath( - os.path.join(tmpdir, 'fake_dists')) - fake_dists_src = os.path.abspath( - os.path.join(os.path.dirname(__file__), 'fake_dists')) - shutil.copytree(fake_dists_src, self.fake_dists_path) - # XXX ugly workaround: revert copystat calls done by shutil behind our - # back (to avoid getting a read-only copy of a read-only file). we - # could pass a custom copy_function to change the mode of files, but - # shutil gives no control over the mode of directories :( - # see http://bugs.python.org/issue1666318 - for root, dirs, files in os.walk(self.fake_dists_path): - os.chmod(root, 0o755) - for f in files: - os.chmod(os.path.join(root, f), 0o644) - for d in dirs: - os.chmod(os.path.join(root, d), 0o755) - - -class CommonDistributionTests(FakeDistsMixin): - """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 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) - - @requires_zlib - 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)) - - @requires_zlib - 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, - lineterminator='\n') - - 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((path, f))) - for file in ('INSTALLER', 'METADATA', 'REQUESTED'): - record_writer.writerow(record_pieces((distinfo_dir, file))) - record_writer.writerow([record_file]) - - with open(record_file) as file: - record_reader = csv.reader(file, lineterminator='\n') - record_data = {} - for row in record_reader: - if row == []: - continue - path, md5_, size = (row[:] + - [None for i in range(len(row), 3)]) - record_data[path] = md5_, size - self.records[distinfo_dir] = record_data - - 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 = os.path.join(*true_path) - false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff', - '__init__.py'] - false_path = os.path.join(*false_path) - - # Test if the distribution uses the file in question - dist = Distribution(distinfo_dir) - self.assertTrue(dist.uses(true_path), 'dist %r is supposed to use %r' % - (dist, true_path)) - self.assertFalse(dist.uses(false_path), 'dist %r is not supposed to ' - 'use %r' % (dist, true_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): - 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_files = [os.path.join(distinfo_dir, filename) for filename in - os.listdir(distinfo_dir)] - found = dist.list_distinfo_files() - self.assertEqual(sorted(found), sorted(distinfo_files)) - # Test for the iteration of local absolute paths - distinfo_files = [os.path.join(sys.prefix, distinfo_dir, path) for - path in distinfo_files] - found = sorted(dist.list_distinfo_files(local=True)) - if os.sep != '/': - self.assertNotIn('/', found[0]) - self.assertIn(os.sep, found[0]) - self.assertEqual(found, sorted(distinfo_files)) - - 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, - FakeDistsMixin, - unittest.TestCase): - - def setUp(self): - super(TestDatabase, self).setUp() - sys.path.insert(0, self.fake_dists_path) - self.addCleanup(sys.path.remove, self.fake_dists_path) - - def test_caches(self): - # sanity check for internal caches - for name in ('_cache_name', '_cache_name_egg', - '_cache_path', '_cache_path_egg'): - self.assertEqual(getattr(packaging.database, name), {}) - - 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) - - @requires_zlib - 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.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.version)) - else: - self.assertFalse(dist.path.startswith(self.fake_dists_path)) - - self.assertEqual(sorted(fake_dists), sorted(found_dists)) - - @requires_zlib - 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) - - @requires_zlib - 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, []) - - @requires_zlib - 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']) - - @requires_zlib - 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, sys.path)) - - found = [(dist.name, dist.version) - for dist in _yield_distributions(False, True, sys.path) - if dist.path.startswith(self.fake_dists_path)] - checkLists(eggs, found) - - found = [(dist.name, dist.version) - for dist in _yield_distributions(True, False, sys.path) - if dist.path.startswith(self.fake_dists_path)] - checkLists(dists, found) - - found = [(dist.name, dist.version) - for dist in _yield_distributions(True, True, sys.path) - if dist.path.startswith(self.fake_dists_path)] - checkLists(dists + eggs, found) - - -class DataFilesTestCase(GlobTestCaseBase): - - def assertRulesMatch(self, rules, spec): - tempdir = self.build_files_tree(spec) - expected = self.clean_tree(spec) - result = get_resources_dests(tempdir, rules) - self.assertEqual(expected, result) - - def clean_tree(self, spec): - files = {} - for path, value in spec.items(): - if value is not None: - files[path] = value - return files - - def test_simple_glob(self): - rules = [('', '*.tpl', '{data}')] - spec = {'coucou.tpl': '{data}/coucou.tpl', - 'Donotwant': None} - self.assertRulesMatch(rules, spec) - - def test_multiple_match(self): - rules = [('scripts', '*.bin', '{appdata}'), - ('scripts', '*', '{appscript}')] - spec = {'scripts/script.bin': '{appscript}/script.bin', - 'Babarlikestrawberry': None} - self.assertRulesMatch(rules, spec) - - def test_set_match(self): - rules = [('scripts', '*.{bin,sh}', '{appscript}')] - spec = {'scripts/script.bin': '{appscript}/script.bin', - 'scripts/babar.sh': '{appscript}/babar.sh', - 'Babarlikestrawberry': None} - self.assertRulesMatch(rules, spec) - - def test_set_match_multiple(self): - rules = [('scripts', 'script{s,}.{bin,sh}', '{appscript}')] - spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', - 'scripts/script.sh': '{appscript}/script.sh', - 'Babarlikestrawberry': None} - self.assertRulesMatch(rules, spec) - - def test_set_match_exclude(self): - rules = [('scripts', '*', '{appscript}'), - ('', os.path.join('**', '*.sh'), None)] - spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', - 'scripts/script.sh': None, - 'Babarlikestrawberry': None} - self.assertRulesMatch(rules, spec) - - def test_glob_in_base(self): - rules = [('scrip*', '*.bin', '{appscript}')] - spec = {'scripts/scripts.bin': '{appscript}/scripts.bin', - 'scripouille/babar.bin': '{appscript}/babar.bin', - 'scriptortu/lotus.bin': '{appscript}/lotus.bin', - 'Babarlikestrawberry': None} - self.assertRulesMatch(rules, spec) - - def test_recursive_glob(self): - rules = [('', os.path.join('**', '*.bin'), '{binary}')] - spec = {'binary0.bin': '{binary}/binary0.bin', - 'scripts/binary1.bin': '{binary}/scripts/binary1.bin', - 'scripts/bin/binary2.bin': '{binary}/scripts/bin/binary2.bin', - 'you/kill/pandabear.guy': None} - self.assertRulesMatch(rules, spec) - - def test_final_exemple_glob(self): - rules = [ - ('mailman/database/schemas/', '*', '{appdata}/schemas'), - ('', os.path.join('**', '*.tpl'), '{appdata}/templates'), - ('', os.path.join('developer-docs', '**', '*.txt'), '{doc}'), - ('', 'README', '{doc}'), - ('mailman/etc/', '*', '{config}'), - ('mailman/foo/', os.path.join('**', 'bar', '*.cfg'), - '{config}/baz'), - ('mailman/foo/', os.path.join('**', '*.cfg'), '{config}/hmm'), - ('', 'some-new-semantic.sns', '{funky-crazy-category}'), - ] - spec = { - 'README': '{doc}/README', - 'some.tpl': '{appdata}/templates/some.tpl', - 'some-new-semantic.sns': - '{funky-crazy-category}/some-new-semantic.sns', - 'mailman/database/mailman.db': None, - 'mailman/database/schemas/blah.schema': - '{appdata}/schemas/blah.schema', - 'mailman/etc/my.cnf': '{config}/my.cnf', - 'mailman/foo/some/path/bar/my.cfg': - '{config}/hmm/some/path/bar/my.cfg', - 'mailman/foo/some/path/other.cfg': - '{config}/hmm/some/path/other.cfg', - 'developer-docs/index.txt': '{doc}/developer-docs/index.txt', - 'developer-docs/api/toc.txt': '{doc}/developer-docs/api/toc.txt', - } - self.maxDiff = None - self.assertRulesMatch(rules, spec) - - def test_get_file(self): - # Create a fake dist - temp_site_packages = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, temp_site_packages) - - dist_name = 'test' - dist_info = os.path.join(temp_site_packages, 'test-0.1.dist-info') - os.mkdir(dist_info) - - metadata_path = os.path.join(dist_info, 'METADATA') - resources_path = os.path.join(dist_info, 'RESOURCES') - - with open(metadata_path, 'w') as fp: - fp.write(dedent("""\ - Metadata-Version: 1.2 - Name: test - Version: 0.1 - Summary: test - Author: me - """)) - - test_path = 'test.cfg' - - fd, test_resource_path = tempfile.mkstemp() - os.close(fd) - self.addCleanup(os.remove, test_resource_path) - - with open(test_resource_path, 'w') as fp: - fp.write('Config') - - with open(resources_path, 'w') as fp: - fp.write('%s,%s' % (test_path, test_resource_path)) - - # Add fake site-packages to sys.path to retrieve fake dist - self.addCleanup(sys.path.remove, temp_site_packages) - sys.path.insert(0, temp_site_packages) - - # Force packaging.database to rescan the sys.path - self.addCleanup(enable_cache) - disable_cache() - - # Try to retrieve resources paths and files - self.assertEqual(get_file_path(dist_name, test_path), - test_resource_path) - self.assertRaises(KeyError, get_file_path, dist_name, 'i-dont-exist') - - with get_file(dist_name, test_path) as fp: - self.assertEqual(fp.read(), 'Config') - self.assertRaises(KeyError, get_file, dist_name, 'i-dont-exist') - - -def test_suite(): - suite = unittest.TestSuite() - load = unittest.defaultTestLoader.loadTestsFromTestCase - suite.addTest(load(TestDistribution)) - suite.addTest(load(TestEggInfoDistribution)) - suite.addTest(load(TestDatabase)) - suite.addTest(load(DataFilesTestCase)) - 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 deleted file mode 100644 index 88333020d3c4..000000000000 --- a/Lib/packaging/tests/test_depgraph.py +++ /dev/null @@ -1,310 +0,0 @@ -"""Tests for packaging.depgraph """ -import os -import re -import sys -from io import StringIO - -from packaging import depgraph -from packaging.database import get_distribution, enable_cache, disable_cache - -from packaging.tests import unittest, support -from packaging.tests.support import requires_zlib - - -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