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 cc5c8e3794..c0c6af34d9 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 c8dd9f46a8..ace8280894 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 bb2e9c58f1..0000000000 --- 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 b3e655b069..0000000000 --- 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 a473bfe35f..0000000000 --- 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 c6106de77d..0000000000 --- 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 d472c248e2..0000000000 --- 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 53a69aecd7..11a29493a5 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 5e562473e2..0000000000 --- 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 6a85351728..0000000000 --- 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 ecf641e391..0000000000 --- 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 9d750f00d9..0000000000 --- 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 c384788e9b..0000000000 --- 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 25cb62bc4b..0000000000 --- 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 199cbcd211..0000000000 --- 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 3e00750988..0000000000 --- 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 332d69df4f..0000000000 --- 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 aaaaab7fb8..0000000000 --- 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 14602cefc1..0000000000 --- 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 f579b18a4c..0000000000 --- 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 5242e4c530..0000000000 --- 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 c6bff47015..0000000000 --- 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 f3b77203f4..0000000000 --- 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 e628c32c6a..0000000000 --- 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 f36cdaba10..0000000000 --- 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 07eadb4faa..b67fbfc281 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 b9878973c7..071706a428 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 035e8d6587..5c1e9ad2f6 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 1d9a3498a7..0000000000 --- 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 b261d00270..0000000000 --- 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 2165b56fd4..0000000000 --- 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 825b5cb558..0000000000 --- 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 594ade01d7..0000000000 --- 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 f2d3863171..0000000000 --- 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 d3d0dec4de..0000000000 --- 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 a757ffc38b..0000000000 --- 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 cd1d598631..0000000000 --- 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 a38101751e..0000000000 --- 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 cafde20e54..0000000000 --- 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 2cedc15ea9..0000000000 --- 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 04f41e519d..0000000000 --- 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 297518bb83..0000000000 --- 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 abe17f3d23..7f8547020f 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 e813f93d22..05b7c65d4d 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 40e850e9ed..b14f3704dc 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 88a9de62c8..2d28246f8d 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 c6225c39c1..25a0ece768 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 93b611765c..0000000000 --- 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 f527bc49ed..0000000000 --- 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 87227c0b69..0000000000 --- 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 e390cdc369..0000000000 --- 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 548e3c4930..0000000000 --- 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 995eec57e5..0000000000 --- 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 3c66360ecd..0000000000 --- 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 fcb50df4e4..0000000000 --- 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 5388ccd4d4..0000000000 --- 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 7aa0b3a741..0000000000 --- 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 00621400da..0000000000 --- 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 d651ae01c6..0000000000 --- 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 6715db90b3..0000000000 --- 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 4f60f4ea93..0000000000 --- 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 25e6a72f2c..0000000000 --- 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 a12d32bfb3..0000000000 --- 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 264c1396b2..0000000000 --- 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 9ca6279533..0000000000 --- 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 8388dc9fe0..0000000000 --- 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 b49729f5af..0000000000 --- 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 e043d6b8ed..0000000000 --- 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 ffc5d457e7..0000000000 --- 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 cfacbe25fb..0000000000 --- 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 59805f7d47..0000000000 --- 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 d39998119f..0000000000 --- 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 4d5348f0c1..0000000000 --- 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 f56d2c69fe..0000000000 --- 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 30e37b52c9..0000000000 --- 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 HcmV?d00001 literal 222208 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�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 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 HcmV?d00001 literal 190464 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
ab*M?cP;NK4Fr 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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 HcmV?d00001 literal 61440 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
w*&Mt&Dk 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 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 HcmV?d00001 literal 65536 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 ..." % 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 2993ebbe71..0000000000 --- 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 5660c50ade..0000000000 --- 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 305fca9cc8..0000000000 --- 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 541465e63f..0000000000 --- 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 2191ac100c..0000000000 --- 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 a646acff3c..0000000000 --- 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 e26d55d002..0000000000 --- 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 945d08abb7..0000000000 --- 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 befdf6dbbb..0000000000 --- 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 c3600a7048..0000000000 --- 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 2b4358aae6..0000000000 --- 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 f48546e5a8..0000000000 --- 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 dff8d00567..0000000000 --- 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 4b3906a41b..0000000000 --- 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 cb820044f4..0000000000 --- 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 00f323e154..0000000000 --- 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 e69de29bb2..0000000000 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 65e839a01f..0000000000 --- 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 5d0da494a5..0000000000 --- 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 ecd6efe9a6..0000000000 --- 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 e69de29bb2..0000000000 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 a176dfdfde..0000000000 --- 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 a7e118a88f..0000000000 --- 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 e69de29bb2..0000000000 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 8b13789179..0000000000 --- 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 5d3e5f68c7..0000000000 --- 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 8b13789179..0000000000 --- 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 4354305659..0000000000 --- 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 e69de29bb2..0000000000 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 27cbe3014d..0000000000 --- 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 e69de29bb2..0000000000 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 418929ecca..0000000000 --- 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 40a96afc6f..0000000000 --- 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 c4027f36c1..0000000000 --- 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 342b8ea851..0000000000 --- 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 499a083e40..0000000000 --- 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 e69de29bb2..0000000000 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 0b99f5249a..0000000000 --- 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 40a96afc6f..0000000000 --- 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 66ba796c3d..0000000000 --- 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 0c58ec1ce9..0000000000 --- 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 HcmV?d00001 literal 1402 zcmZ{jUq}=|9LM)O|8-_iiGR>!yw1+cT>pgP$yW|r9$$83v78&fIIYEXJPRUTFJu{3=R#sAsh76<0 zln3k*asuD6G>e>0MrfcRBfG3*J7#zuZ1ZghVHrz|=s5wlaX7_0mtr#Mf!+plOUfhsO7O*>anQkjmC zXysumPk!1+2U$1Eu4^)3P6h@ zdcvua=9l?DmJ7@GllynH&QpX=Vm)+?iG%V z=st~9B_Fudv(!J9G*S04eqgh}RCRYL|77;n2lLfK-@D%w%hO%0Az|ke-i^M;_Q!Es zpDi2V!FW^Xs9hh1#OUX2JO9&WmT}(ddcz)tr)BVDW8(P%w(b2xJ3%SLn9>d@lg{8ZnS z>D0HqwLdkdE$iji*CKqL2KVpmQJmTZD3Cr|MZ^hM_8J>QOjT-^8wye}mZE-}Yw~(S z7qc{Etke-w-nKVD5ef}ZU|m+86Pnyu5*bZUqIR#)#FG7 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 1b92891b02..0000000000 --- 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 13c30cf40b..0000000000 --- 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 8c8c641eb0..0000000000 --- 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 HcmV?d00001 literal 110 zcmb2|=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 e69de29bb2..0000000000 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 b89f1bdb84..0000000000 --- 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 9e42b16d23..0000000000 --- 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 9baee0479e..0000000000 --- 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 c3d42c5692..0000000000 --- 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 4f34312a30..0000000000 --- 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 0565e11bdd..0000000000 --- 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 a70cfd345f..0000000000 --- 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 b36d728a0b..0000000000 --- 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 a282a4ea31..0000000000 --- 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 a1a7bb7282..0000000000 --- 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 265ee0af95..0000000000 --- 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 6f976676f5..0000000000 --- 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 92e4702f63..0000000000 --- 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 b100a26542..0000000000 --- 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 a1a7bb7282..0000000000 --- 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 1cc0c32f1b..0000000000 --- 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 f6ace22054..0000000000 --- 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 171df9360c..0000000000 --- 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 a1a7bb7282..0000000000 --- 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 b2885ae384..0000000000 --- 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 a1a7bb7282..0000000000 --- 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 d76d3dbdee..0000000000 --- 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 dd4bdd9d95..0000000000 --- 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 7b2ea013ab..0000000000 --- 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 15cf6586e4..0000000000 --- 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 86754a8ce5..0000000000 --- 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 09bdaadfc9..0000000000 --- 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 280d709d8d..0000000000 --- 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 a2a8583b0f..0000000000 --- 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 9a00c116a5..0000000000 --- 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 0599bf23bf..0000000000 --- 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 fd3ac246a6..0000000000 --- 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 0b91050570..0000000000 --- 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 a78c3a7a37..0000000000 --- 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 6d00ec3737..0000000000 --- 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 dae75b4f87..0000000000 --- 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 8d4373d685..0000000000 --- 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 3345d2e39a..0000000000 --- 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 33153e7fe3..0000000000 --- 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 f2906a7e18..0000000000 --- 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 79e8fa8196..0000000000 --- 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 6452a3455a..0000000000 --- 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 07fad8998e..0000000000 --- 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 d9747189d1..0000000000 --- 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 7aa1f79910..0000000000 --- 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 1f68c1d96b..0000000000 --- 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 803e733d30..0000000000 --- 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 2c620cb513..0000000000 --- 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 0d76b29a04..0000000000 --- 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 76bc3316b4..0000000000 --- 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 17c43cd28a..0000000000 --- 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 ad91b94ca3..0000000000 --- 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 88333020d3..0000000000 --- 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

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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 HcmV?d00001 literal 223744 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 r^A+wA7Ib9KOZFnF6;^o&F%T6)Q%lh`woQgW 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 diff --git a/Lib/packaging/compat.py b/Lib/packaging/compat.py deleted file mode 100644 index bfce92de66..0000000000 --- a/Lib/packaging/compat.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Support for build-time 2to3 conversion.""" - -from packaging import logger - - -# XXX Having two classes with the same name is not a good thing. -# XXX 2to3-related code should move from util to this module - -try: - from packaging.util import Mixin2to3 as _Mixin2to3 - _CONVERT = True - _KLASS = _Mixin2to3 -except ImportError: - _CONVERT = False - _KLASS = object - -__all__ = ['Mixin2to3'] - - -class Mixin2to3(_KLASS): - """ The base class which can be used for refactoring. When run under - Python 3.0, the run_2to3 method provided by Mixin2to3 is overridden. - When run on Python 2.x, it merely creates a class which overrides run_2to3, - yet does nothing in particular with it. - """ - if _CONVERT: - - def _run_2to3(self, files=[], doctests=[], fixers=[]): - """ Takes a list of files and doctests, and performs conversion - on those. - - First, the files which contain the code(`files`) are converted. - - Second, the doctests in `files` are converted. - - Thirdly, the doctests in `doctests` are converted. - """ - if fixers: - self.fixer_names = fixers - - if files: - logger.info('converting Python code and doctests') - _KLASS.run_2to3(self, files) - _KLASS.run_2to3(self, files, doctests_only=True) - - if doctests: - logger.info('converting doctests in text files') - _KLASS.run_2to3(self, doctests, doctests_only=True) - else: - # If run on Python 2.x, there is nothing to do. - - def _run_2to3(self, files=[], doctests=[], fixers=[]): - pass diff --git a/Lib/packaging/compiler/__init__.py b/Lib/packaging/compiler/__init__.py deleted file mode 100644 index d8e02ce392..0000000000 --- a/Lib/packaging/compiler/__init__.py +++ /dev/null @@ -1,274 +0,0 @@ -"""Compiler abstraction model used by packaging. - -An abstract base class is defined in the ccompiler submodule, and -concrete implementations suitable for various platforms are defined in -the other submodules. The extension module is also placed in this -package. - -In general, code should not instantiate compiler classes directly but -use the new_compiler and customize_compiler functions provided in this -module. - -The compiler system has a registration API: get_default_compiler, -set_compiler, show_compilers. -""" - -import os -import sys -import re -import sysconfig - -from packaging.util import resolve_name -from packaging.errors import PackagingPlatformError -from packaging import logger - -def customize_compiler(compiler): - """Do any platform-specific customization of a CCompiler instance. - - Mainly needed on Unix, so we can plug in the information that - varies across Unices and is stored in Python's Makefile. - """ - if compiler.name == "unix": - cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags = ( - sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS', - 'CCSHARED', 'LDSHARED', 'SO', 'AR', - 'ARFLAGS')) - - if 'CC' in os.environ: - cc = os.environ['CC'] - if 'CXX' in os.environ: - cxx = os.environ['CXX'] - if 'LDSHARED' in os.environ: - ldshared = os.environ['LDSHARED'] - if 'CPP' in os.environ: - cpp = os.environ['CPP'] - else: - cpp = cc + " -E" # not always - if 'LDFLAGS' in os.environ: - ldshared = ldshared + ' ' + os.environ['LDFLAGS'] - if 'CFLAGS' in os.environ: - cflags = opt + ' ' + os.environ['CFLAGS'] - ldshared = ldshared + ' ' + os.environ['CFLAGS'] - if 'CPPFLAGS' in os.environ: - cpp = cpp + ' ' + os.environ['CPPFLAGS'] - cflags = cflags + ' ' + os.environ['CPPFLAGS'] - ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] - if 'AR' in os.environ: - ar = os.environ['AR'] - if 'ARFLAGS' in os.environ: - archiver = ar + ' ' + os.environ['ARFLAGS'] - else: - if ar_flags is not None: - archiver = ar + ' ' + ar_flags - else: - # see if its the proper default value - # mmm I don't want to backport the makefile - archiver = ar + ' rc' - - cc_cmd = cc + ' ' + cflags - compiler.set_executables( - preprocessor=cpp, - compiler=cc_cmd, - compiler_so=cc_cmd + ' ' + ccshared, - compiler_cxx=cxx, - linker_so=ldshared, - linker_exe=cc, - archiver=archiver) - - compiler.shared_lib_extension = so_ext - - -# Map a sys.platform/os.name ('posix', 'nt') to the default compiler -# type for that platform. Keys are interpreted as re match -# patterns. Order is important; platform mappings are preferred over -# OS names. -_default_compilers = ( - # Platform string mappings - - # on a cygwin built python we can use gcc like an ordinary UNIXish - # compiler - ('cygwin.*', 'unix'), - - # OS name mappings - ('posix', 'unix'), - ('nt', 'msvc'), -) - -def get_default_compiler(osname=None, platform=None): - """ 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 in case the - parameters are not given. - - """ - if osname is None: - osname = os.name - if platform is None: - platform = sys.platform - for pattern, compiler in _default_compilers: - if re.match(pattern, platform) is not None or \ - re.match(pattern, osname) is not None: - return compiler - # Defaults to Unix compiler - return 'unix' - - -# compiler mapping -# XXX useful to expose them? (i.e. get_compiler_names) -_COMPILERS = { - 'unix': 'packaging.compiler.unixccompiler.UnixCCompiler', - 'msvc': 'packaging.compiler.msvccompiler.MSVCCompiler', - 'cygwin': 'packaging.compiler.cygwinccompiler.CygwinCCompiler', - 'mingw32': 'packaging.compiler.cygwinccompiler.Mingw32CCompiler', - 'bcpp': 'packaging.compiler.bcppcompiler.BCPPCompiler', -} - -def set_compiler(location): - """Add or change a compiler""" - cls = resolve_name(location) - # XXX we want to check the class here - _COMPILERS[cls.name] = cls - - -def show_compilers(): - """Print list of available compilers (used by the "--help-compiler" - options to "build", "build_ext", "build_clib"). - """ - from packaging.fancy_getopt import FancyGetopt - compilers = [] - - for name, cls in _COMPILERS.items(): - if isinstance(cls, str): - cls = resolve_name(cls) - _COMPILERS[name] = cls - - compilers.append(("compiler=" + name, None, cls.description)) - - compilers.sort() - pretty_printer = FancyGetopt(compilers) - pretty_printer.print_help("List of available compilers:") - - -def new_compiler(plat=None, compiler=None, dry_run=False, force=False): - """Generate an instance of some CCompiler subclass for the supplied - platform/compiler combination. 'plat' defaults to 'os.name' - (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler - for that platform. Currently only 'posix' and 'nt' are supported, and - the default compilers are "traditional Unix interface" (UnixCCompiler - class) and Visual C++ (MSVCCompiler class). Note that it's perfectly - possible to ask for a Unix compiler object under Windows, and a - Microsoft compiler object under Unix -- if you supply a value for - 'compiler', 'plat' is ignored. - """ - if plat is None: - plat = os.name - - try: - if compiler is None: - compiler = get_default_compiler(plat) - - cls = _COMPILERS[compiler] - except KeyError: - msg = "don't know how to compile C/C++ code on platform '%s'" % plat - if compiler is not None: - msg = msg + " with '%s' compiler" % compiler - raise PackagingPlatformError(msg) - - if isinstance(cls, str): - cls = resolve_name(cls) - _COMPILERS[compiler] = cls - - return cls(dry_run, force) - - -def gen_preprocess_options(macros, include_dirs): - """Generate C pre-processor options (-D, -U, -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 (-U) macro 'name', and (name,value) means define (-D) - macro 'name' to 'value'. 'include_dirs' is just a list of directory - names to be added to the header file search path (-I). Returns a list - of command-line options suitable for either Unix compilers or Visual - C++. - """ - # XXX it would be nice (mainly aesthetic, and so we don't generate - # stupid-looking command lines) to go over 'macros' and eliminate - # redundant definitions/undefinitions (ie. ensure that only the - # latest mention of a particular macro winds up on the command - # line). I don't think it's essential, though, since most (all?) - # Unix C compilers only pay attention to the latest -D or -U - # mention of a macro on their command line. Similar situation for - # 'include_dirs'. I'm punting on both for now. Anyways, weeding out - # redundancies like this should probably be the province of - # CCompiler, since the data structures used are inherited from it - # and therefore common to all CCompiler classes. - - pp_opts = [] - for macro in macros: - - if not isinstance(macro, tuple) and 1 <= len(macro) <= 2: - raise TypeError( - "bad macro definition '%s': each element of 'macros'" - "list must be a 1- or 2-tuple" % macro) - - if len(macro) == 1: # undefine this macro - pp_opts.append("-U%s" % macro[0]) - elif len(macro) == 2: - if macro[1] is None: # define with no explicit value - pp_opts.append("-D%s" % macro[0]) - else: - # XXX *don't* need to be clever about quoting the - # macro value here, because we're going to avoid the - # shell at all costs when we spawn the command! - pp_opts.append("-D%s=%s" % macro) - - for dir in include_dirs: - pp_opts.append("-I%s" % dir) - - return pp_opts - - -def 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). - """ - lib_opts = [] - - for dir in library_dirs: - lib_opts.append(compiler.library_dir_option(dir)) - - for dir in runtime_library_dirs: - opt = compiler.runtime_library_dir_option(dir) - if isinstance(opt, list): - lib_opts.extend(opt) - else: - lib_opts.append(opt) - - # XXX it's important that we *not* remove redundant library mentions! - # sometimes you really do have to say "-lfoo -lbar -lfoo" in order to - # resolve all symbols. I just hope we never have to say "-lfoo obj.o - # -lbar" to get things to work -- that's certainly a possibility, but a - # pretty nasty way to arrange your C code. - - for lib in libraries: - lib_dir, lib_name = os.path.split(lib) - if lib_dir != '': - lib_file = compiler.find_library_file([lib_dir], lib_name) - if lib_file is not None: - lib_opts.append(lib_file) - else: - logger.warning("no library file corresponding to " - "'%s' found (skipping)" % lib) - else: - lib_opts.append(compiler.library_option(lib)) - - return lib_opts diff --git a/Lib/packaging/compiler/bcppcompiler.py b/Lib/packaging/compiler/bcppcompiler.py deleted file mode 100644 index 06c758cbe0..0000000000 --- a/Lib/packaging/compiler/bcppcompiler.py +++ /dev/null @@ -1,355 +0,0 @@ -"""CCompiler implementation for the Borland C++ compiler.""" - -# This implementation by Lyle Johnson, based on the original msvccompiler.py -# module and using the directions originally published by Gordon Williams. - -# XXX looks like there's a LOT of overlap between these two classes: -# someone should sit down and factor out the common code as -# WindowsCCompiler! --GPW - -import os - -from packaging.errors import (PackagingExecError, CompileError, LibError, - LinkError, UnknownFileError) -from packaging.compiler.ccompiler import CCompiler -from packaging.compiler import gen_preprocess_options -from packaging.file_util import write_file -from packaging.dep_util import newer -from packaging import logger - - -class BCPPCompiler(CCompiler) : - """Concrete class that implements an interface to the Borland C/C++ - compiler, as defined by the CCompiler abstract class. - """ - - name = 'bcpp' - description = 'Borland C++ Compiler' - - # 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'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = _c_extensions + _cpp_extensions - 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(BCPPCompiler, self).__init__(dry_run, force) - - # These executables are assumed to all be in the path. - # Borland doesn't seem to use any special registry settings to - # indicate their installation locations. - - self.cc = "bcc32.exe" - self.linker = "ilink32.exe" - self.lib = "tlib.exe" - - self.preprocess_options = None - self.compile_options = ['/tWM', '/O2', '/q', '/g0'] - self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0'] - - self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x'] - self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x'] - self.ldflags_static = [] - self.ldflags_exe = ['/Gn', '/q', '/x'] - self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] - - - # -- Worker methods ------------------------------------------------ - - def compile(self, sources, - output_dir=None, macros=None, include_dirs=None, debug=False, - extra_preargs=None, extra_postargs=None, depends=None): - - 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 - # XXX why do the normpath here? - src = os.path.normpath(src) - obj = os.path.normpath(obj) - # XXX _setup_compile() did a mkpath() too but before the normpath. - # Is it possible to skip the normpath? - self.mkpath(os.path.dirname(obj)) - - if ext == '.res': - # This is already a binary file -- skip it. - continue # the 'for' loop - if ext == '.rc': - # This needs to be compiled to a .res file -- do it now. - try: - self.spawn(["brcc32", "-fo", obj, src]) - except PackagingExecError as msg: - raise CompileError(msg) - continue # the 'for' loop - - # The next two are both for the real compiler. - if ext in self._c_extensions: - input_opt = "" - elif ext in self._cpp_extensions: - input_opt = "-P" - else: - # Unknown file type -- no extra options. The compiler - # will probably fail, but let it just in case this is a - # file the compiler recognizes even if we don't. - input_opt = "" - - output_opt = "-o" + obj - - # Compiler command line syntax is: "bcc32 [options] file(s)". - # Note that the source file names must appear at the end of - # the command line. - try: - self.spawn([self.cc] + compile_opts + pp_opts + - [input_opt, output_opt] + - extra_postargs + [src]) - 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): - 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 = [output_filename, '/u'] + objects - 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): - - # XXX this ignores 'build_temp'! should follow the lead of - # msvccompiler.py - - 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: - logger.warning("don't know what to do with " - "'runtime_library_dirs': %r", runtime_library_dirs) - - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - - # Figure out linker args based on type of target. - if target_desc == CCompiler.EXECUTABLE: - startup_obj = 'c0w32' - if debug: - ld_args = self.ldflags_exe_debug[:] - else: - ld_args = self.ldflags_exe[:] - else: - startup_obj = 'c0d32' - if debug: - ld_args = self.ldflags_shared_debug[:] - else: - ld_args = self.ldflags_shared[:] - - - # Create a temporary exports file for use by the linker - if export_symbols is None: - def_file = '' - else: - head, tail = os.path.split(output_filename) - modname, ext = os.path.splitext(tail) - temp_dir = os.path.dirname(objects[0]) # preserve tree structure - def_file = os.path.join(temp_dir, '%s.def' % modname) - contents = ['EXPORTS'] - for sym in (export_symbols or []): - contents.append(' %s=_%s' % (sym, sym)) - self.execute(write_file, (def_file, contents), - "writing %s" % def_file) - - # Borland C++ has problems with '/' in paths - objects2 = [os.path.normpath(o) for o in objects] - # split objects in .obj and .res files - # Borland C++ needs them at different positions in the command line - objects = [startup_obj] - resources = [] - for file in objects2: - base, ext = os.path.splitext(os.path.normcase(file)) - if ext == '.res': - resources.append(file) - else: - objects.append(file) - - - for l in library_dirs: - ld_args.append("/L%s" % os.path.normpath(l)) - ld_args.append("/L.") # we sometimes use relative paths - - # list of object files - ld_args.extend(objects) - - # XXX the command line syntax for Borland C++ is a bit wonky; - # certain filenames are jammed together in one big string, but - # comma-delimited. This doesn't mesh too well with the - # Unix-centric attitude (with a DOS/Windows quoting hack) of - # 'spawn()', so constructing the argument list is a bit - # awkward. Note that doing the obvious thing and jamming all - # the filenames and commas into one argument would be wrong, - # because 'spawn()' would quote any filenames with spaces in - # them. Arghghh!. Apparently it works fine as coded... - - # name of dll/exe file - ld_args.extend((',',output_filename)) - # no map file and start libraries - ld_args.append(',,') - - for lib in libraries: - # see if we find it and if there is a bcpp specific lib - # (xxx_bcpp.lib) - libfile = self.find_library_file(library_dirs, lib, debug) - if libfile is None: - ld_args.append(lib) - # probably a BCPP internal library -- don't warn - else: - # full name which prefers bcpp_xxx.lib over xxx.lib - ld_args.append(libfile) - - # some default libraries - ld_args.append('import32') - ld_args.append('cw32mt') - - # def file for export symbols - ld_args.extend((',',def_file)) - # add resource files - ld_args.append(',') - ld_args.extend(resources) - - - 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 ----------------------------------------- - - - def find_library_file(self, dirs, lib, debug=False): - # List of effective library names to try, in order of preference: - # xxx_bcpp.lib is better than xxx.lib - # and xxx_d.lib is better than xxx.lib if debug is set - # - # The "_bcpp" suffix is to handle a Python installation for people - # with multiple compilers (primarily Packaging hackers, I suspect - # ;-). The idea is they'd have one static library for each - # compiler they care about, since (almost?) every Windows compiler - # seems to have a different format for static libraries. - if debug: - dlib = (lib + "_d") - try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib) - else: - try_names = (lib + "_bcpp", 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 - - # overwrite the one from CCompiler to support rc and res-files - 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: - # 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 == '.res': - # these can go unchanged - obj_names.append(os.path.join(output_dir, base + ext)) - elif ext == '.rc': - # these need to be compiled to .res-files - obj_names.append(os.path.join(output_dir, base + '.res')) - else: - obj_names.append(os.path.join(output_dir, - base + self.obj_extension)) - return obj_names - - - def preprocess(self, source, output_file=None, macros=None, - include_dirs=None, extra_preargs=None, - extra_postargs=None): - _, macros, include_dirs = \ - self._fix_compile_args(None, macros, include_dirs) - pp_opts = gen_preprocess_options(macros, include_dirs) - pp_args = ['cpp32.exe'] + pp_opts - if output_file is not None: - pp_args.append('-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 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) diff --git a/Lib/packaging/compiler/ccompiler.py b/Lib/packaging/compiler/ccompiler.py deleted file mode 100644 index 98c4b6847d..0000000000 --- a/Lib/packaging/compiler/ccompiler.py +++ /dev/null @@ -1,863 +0,0 @@ -"""Abstract base class for compilers. - -This modules contains CCompiler, an abstract base class that defines the -interface for the compiler abstraction model used by packaging. -""" - -import os -from shutil import move -from packaging import logger -from packaging.util import split_quoted, execute, newer_group, spawn -from packaging.errors import (CompileError, LinkError, UnknownFileError) -from packaging.compiler import gen_preprocess_options - - -class CCompiler: - """Abstract base class to define the interface that must be implemented - by real compiler classes. 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. - """ - - # 'name' is a class attribute that identifies this class. It - # keeps code that wants to know what kind of compiler it's dealing with - # from having to import all possible compiler classes just to do an - # 'isinstance'. - name = None - description = None - - # XXX things not handled by this compiler abstraction model: - # * client can't provide additional options for a compiler, - # e.g. warning, optimization, debugging flags. Perhaps this - # should be the domain of concrete compiler abstraction classes - # (UnixCCompiler, MSVCCompiler, etc.) -- or perhaps the base - # class should have methods for the common ones. - # * can't completely override the include or library searchg - # path, ie. no "cc -I -Idir1 -Idir2" or "cc -L -Ldir1 -Ldir2". - # I'm not sure how widely supported this is even by Unix - # compilers, much less on other platforms. And I'm even less - # sure how useful it is; maybe for cross-compiling, but - # support for that is a ways off. (And anyways, cross - # compilers probably have a dedicated binary with the - # right paths compiled in. I hope.) - # * can't do really freaky things with the library list/library - # dirs, e.g. "-Ldir1 -lfoo -Ldir2 -lfoo" to link against - # different versions of libfoo.a in different locations. I - # think this is useless without the ability to null out the - # library search path anyways. - - - # Subclasses that rely on the standard filename generation methods - # implemented below should override these; see the comment near - # those methods ('object_filenames()' et. al.) for details: - src_extensions = None # list of strings - obj_extension = None # string - static_lib_extension = None - shared_lib_extension = None # string - static_lib_format = None # format string - shared_lib_format = None # prob. same as static_lib_format - exe_extension = None # string - - # Default language settings. language_map is used to detect a source - # file or Extension target language, checking source filenames. - # language_order is used to detect the language precedence, when deciding - # what language to use when mixing source types. For example, if some - # extension has two files with ".c" extension, and one with ".cpp", it - # is still linked as c++. - language_map = {".c": "c", - ".cc": "c++", - ".cpp": "c++", - ".cxx": "c++", - ".m": "objc", - } - language_order = ["c++", "objc", "c"] - - def __init__(self, dry_run=False, force=False): - self.dry_run = dry_run - self.force = force - - # 'output_dir': a common output directory for object, library, - # shared object, and shared library files - self.output_dir = None - - # 'macros': a list of macro definitions (or undefinitions). A - # macro definition is a 2-tuple (name, value), where the value is - # either a string or None (no explicit value). A macro - # undefinition is a 1-tuple (name,). - self.macros = [] - - # 'include_dirs': a list of directories to search for include files - self.include_dirs = [] - - # 'libraries': a list of libraries to include in any link - # (library names, not filenames: eg. "foo" not "libfoo.a") - self.libraries = [] - - # 'library_dirs': a list of directories to search for libraries - self.library_dirs = [] - - # 'runtime_library_dirs': a list of directories to search for - # shared libraries/objects at runtime - self.runtime_library_dirs = [] - - # 'objects': a list of object files (or similar, such as explicitly - # named library files) to include on any link - self.objects = [] - - for key, value in self.executables.items(): - self.set_executable(key, value) - - def set_executables(self, **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: - 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 - 'distutils.util.split_quoted()'.) - """ - - # Note that some CCompiler implementation classes will define class - # attributes 'cpp', 'cc', etc. with hard-coded executable names; - # this is appropriate when a compiler class is for exactly one - # compiler/OS combination (eg. MSVCCompiler). Other compiler - # classes (UnixCCompiler, in particular) are driven by information - # discovered at run-time, since there are many different ways to do - # basically the same things with Unix C compilers. - - for key, value in args.items(): - if key not in self.executables: - raise ValueError("unknown executable '%s' for class %s" % \ - (key, self.__class__.__name__)) - self.set_executable(key, value) - - def set_executable(self, key, value): - if isinstance(value, str): - setattr(self, key, split_quoted(value)) - else: - setattr(self, key, value) - - def _find_macro(self, name): - i = 0 - for defn in self.macros: - if defn[0] == name: - return i - i = i + 1 - return None - - def _check_macro_definitions(self, definitions): - """Ensures that every element of 'definitions' is a valid macro - definition, ie. either (name,value) 2-tuple or a (name,) tuple. Do - nothing if all definitions are OK, raise TypeError otherwise. - """ - for defn in definitions: - if not (isinstance(defn, tuple) and - (len(defn) == 1 or - (len(defn) == 2 and - (isinstance(defn[1], str) or defn[1] is None))) and - isinstance(defn[0], str)): - raise TypeError(("invalid macro definition '%s': " % defn) + \ - "must be tuple (string,), (string, string), or " + \ - "(string, None)") - - - # -- Bookkeeping methods ------------------------------------------- - - def define_macro(self, 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?) - """ - # Delete from the list of macro definitions/undefinitions if - # already there (so that this one will take precedence). - i = self._find_macro(name) - if i is not None: - del self.macros[i] - - defn = (name, value) - self.macros.append(defn) - - def undefine_macro(self, name): - """Undefine a preprocessor macro for all compilations driven by - this compiler object. If the same macro is defined by - 'define_macro()' and undefined by 'undefine_macro()' the last call - takes precedence (including multiple redefinitions or - undefinitions). If the macro is redefined/undefined on a - per-compilation basis (ie. in the call to 'compile()'), then that - takes precedence. - """ - # Delete from the list of macro definitions/undefinitions if - # already there (so that this one will take precedence). - i = self._find_macro(name) - if i is not None: - del self.macros[i] - - undefn = (name,) - self.macros.append(undefn) - - def add_include_dir(self, 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 - 'add_include_dir()'. - """ - self.include_dirs.append(dir) - - def set_include_dirs(self, dirs): - """Set the list of directories that will be searched to 'dirs' (a - list of strings). Overrides any preceding calls to - 'add_include_dir()'; subsequence calls to 'add_include_dir()' add - to the list passed to 'set_include_dirs()'. This does not affect - any list of standard include directories that the compiler may - search by default. - """ - self.include_dirs = dirs[:] - - def add_library(self, 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 'add_library()' and/or - '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. - """ - self.libraries.append(libname) - - def set_libraries(self, 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. - """ - self.libraries = libnames[:] - - - def add_library_dir(self, dir): - """Add 'dir' to the list of directories that will be searched for - libraries specified to 'add_library()' and 'set_libraries()'. The - linker will be instructed to search for libraries in the order they - are supplied to 'add_library_dir()' and/or 'set_library_dirs()'. - """ - self.library_dirs.append(dir) - - def set_library_dirs(self, 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. - """ - self.library_dirs = dirs[:] - - def add_runtime_library_dir(self, dir): - """Add 'dir' to the list of directories that will be searched for - shared libraries at runtime. - """ - self.runtime_library_dirs.append(dir) - - def set_runtime_library_dirs(self, 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. - """ - self.runtime_library_dirs = dirs[:] - - def add_link_object(self, 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. - """ - self.objects.append(object) - - def set_link_objects(self, 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). - """ - self.objects = objects[:] - - - # -- Private utility methods -------------------------------------- - # (here for the convenience of subclasses) - - # Helper method to prep compiler in subclass compile() methods - def _setup_compile(self, outdir, macros, incdirs, sources, depends, - extra): - """Process arguments and decide which source files to compile.""" - if outdir is None: - outdir = self.output_dir - elif not isinstance(outdir, str): - raise TypeError("'output_dir' must be a string or None") - - if macros is None: - macros = self.macros - elif isinstance(macros, list): - macros = macros + (self.macros or []) - else: - raise TypeError("'macros' (if supplied) must be a list of tuples") - - if incdirs is None: - incdirs = self.include_dirs - elif isinstance(incdirs, (list, tuple)): - incdirs = list(incdirs) + (self.include_dirs or []) - else: - raise TypeError( - "'include_dirs' (if supplied) must be a list of strings") - - if extra is None: - extra = [] - - # Get the list of expected output (object) files - objects = self.object_filenames(sources, - strip_dir=False, - output_dir=outdir) - assert len(objects) == len(sources) - - pp_opts = gen_preprocess_options(macros, incdirs) - - build = {} - for i in range(len(sources)): - src = sources[i] - obj = objects[i] - ext = os.path.splitext(src)[1] - self.mkpath(os.path.dirname(obj)) - build[obj] = (src, ext) - - return macros, objects, extra, pp_opts, build - - def _get_cc_args(self, pp_opts, debug, before): - # works for unixccompiler and cygwinccompiler - cc_args = pp_opts + ['-c'] - if debug: - cc_args[:0] = ['-g'] - if before: - cc_args[:0] = before - return cc_args - - def _fix_compile_args(self, output_dir, macros, include_dirs): - """Typecheck and fix-up some of the arguments to the 'compile()' - method, and return fixed-up values. Specifically: if 'output_dir' - is None, replaces it with 'self.output_dir'; ensures that 'macros' - is a list, and augments it with 'self.macros'; ensures that - 'include_dirs' is a list, and augments it with 'self.include_dirs'. - Guarantees that the returned values are of the correct type, - i.e. for 'output_dir' either string or None, and for 'macros' and - 'include_dirs' either list or None. - """ - if output_dir is None: - output_dir = self.output_dir - elif not isinstance(output_dir, str): - raise TypeError("'output_dir' must be a string or None") - - if macros is None: - macros = self.macros - elif isinstance(macros, list): - macros = macros + (self.macros or []) - else: - raise TypeError("'macros' (if supplied) must be a list of tuples") - - if include_dirs is None: - include_dirs = self.include_dirs - elif isinstance(include_dirs, (list, tuple)): - include_dirs = list(include_dirs) + (self.include_dirs or []) - else: - raise TypeError( - "'include_dirs' (if supplied) must be a list of strings") - - return output_dir, macros, include_dirs - - def _fix_object_args(self, objects, output_dir): - """Typecheck and fix up some arguments supplied to various methods. - Specifically: ensure that 'objects' is a list; if output_dir is - None, replace with self.output_dir. Return fixed versions of - 'objects' and 'output_dir'. - """ - if not isinstance(objects, (list, tuple)): - raise TypeError("'objects' must be a list or tuple of strings") - objects = list(objects) - - if output_dir is None: - output_dir = self.output_dir - elif not isinstance(output_dir, str): - raise TypeError("'output_dir' must be a string or None") - - return objects, output_dir - - def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): - """Typecheck and fix up some of the arguments supplied to the - 'link_*' methods. Specifically: ensure that all arguments are - lists, and augment them with their permanent versions - (eg. 'self.libraries' augments 'libraries'). Return a tuple with - fixed versions of all arguments. - """ - if libraries is None: - libraries = self.libraries - elif isinstance(libraries, (list, tuple)): - libraries = list(libraries) + (self.libraries or []) - else: - raise TypeError( - "'libraries' (if supplied) must be a list of strings") - - if library_dirs is None: - library_dirs = self.library_dirs - elif isinstance(library_dirs, (list, tuple)): - library_dirs = list(library_dirs) + (self.library_dirs or []) - else: - raise TypeError( - "'library_dirs' (if supplied) must be a list of strings") - - if runtime_library_dirs is None: - runtime_library_dirs = self.runtime_library_dirs - elif isinstance(runtime_library_dirs, (list, tuple)): - runtime_library_dirs = (list(runtime_library_dirs) + - (self.runtime_library_dirs or [])) - else: - raise TypeError("'runtime_library_dirs' (if supplied) " - "must be a list of strings") - - return libraries, library_dirs, runtime_library_dirs - - def _need_link(self, objects, output_file): - """Return true if we need to relink the files listed in 'objects' - to recreate 'output_file'. - """ - if self.force: - return True - else: - if self.dry_run: - newer = newer_group(objects, output_file, missing='newer') - else: - newer = newer_group(objects, output_file) - return newer - - def detect_language(self, sources): - """Detect the language of a given file, or list of files. Uses - language_map, and language_order to do the job. - """ - if not isinstance(sources, list): - sources = [sources] - lang = None - index = len(self.language_order) - for source in sources: - base, ext = os.path.splitext(source) - extlang = self.language_map.get(ext) - try: - extindex = self.language_order.index(extlang) - if extindex < index: - lang = extlang - index = extindex - except ValueError: - pass - return lang - - # -- Worker methods ------------------------------------------------ - # (must be implemented by subclasses) - - def preprocess(self, 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 'compile()', which will augment the macros set - with 'define_macro()' and 'undefine_macro()'. 'include_dirs' is a - list of directory names that will be added to the default list. - - Raises PreprocessError on failure. - """ - pass - - def compile(self, sources, output_dir=None, macros=None, - include_dirs=None, debug=False, extra_preargs=None, - extra_postargs=None, depends=None): - """Compile one or more source files. - - '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 (eg. MSVCCompiler 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, "foo/bar.c" - normally compiles to "foo/bar.o" (for a Unix implementation); if - 'output_dir' is "build", then it would compile to - "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 prepand/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 CompileError on failure. - """ - # A concrete compiler class can either override this method - # entirely or implement _compile(). - - macros, objects, extra_postargs, pp_opts, build = \ - self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) - - # Return *all* object filenames, not just the ones we just built. - return objects - - def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - """Compile 'src' to product 'obj'.""" - - # A concrete compiler class that does not override compile() - # should implement _compile(). - pass - - def create_static_lib(self, objects, output_libname, output_dir=None, - debug=False, 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 - 'add_link_object()' and/or 'set_link_objects()', the libraries - supplied to 'add_library()' and/or '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. - - '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 LibError on failure. - """ - pass - - # values for target_desc parameter in link() - SHARED_OBJECT = "shared_object" - SHARED_LIBRARY = "shared_library" - EXECUTABLE = "executable" - - 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 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 (eg. "foo" becomes "libfoo.a" - on Unix and "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 - (ie. no directory component). These are on top of the system - default and those supplied to 'add_library_dir()' and/or - '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 'compile()' and 'create_static_lib()', with the - slight distinction that it actually matters on most platforms (as - opposed to 'create_static_lib()', which includes a 'debug' flag - mostly for form's sake). - - 'extra_preargs' and 'extra_postargs' are as for '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 LinkError on failure. - """ - raise NotImplementedError - - - # Old 'link_*()' methods, rewritten to use the new 'link()' method. - - def link_shared_lib(self, objects, output_libname, 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): - self.link(CCompiler.SHARED_LIBRARY, objects, - self.library_filename(output_libname, lib_type='shared'), - output_dir, - libraries, library_dirs, runtime_library_dirs, - export_symbols, debug, - extra_preargs, extra_postargs, build_temp, target_lang) - - def link_shared_object(self, 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): - self.link(CCompiler.SHARED_OBJECT, objects, - output_filename, output_dir, - libraries, library_dirs, runtime_library_dirs, - export_symbols, debug, - extra_preargs, extra_postargs, build_temp, target_lang) - - def link_executable(self, objects, output_progname, output_dir=None, - libraries=None, library_dirs=None, - runtime_library_dirs=None, debug=False, - extra_preargs=None, extra_postargs=None, - target_lang=None): - self.link(CCompiler.EXECUTABLE, objects, - self.executable_filename(output_progname), output_dir, - libraries, library_dirs, runtime_library_dirs, None, - debug, extra_preargs, extra_postargs, None, target_lang) - - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function; there is - # no appropriate default implementation so subclasses should - # implement all of these. - - def library_dir_option(self, dir): - """Return the compiler option to add 'dir' to the list of - directories searched for libraries. - """ - raise NotImplementedError - - def runtime_library_dir_option(self, dir): - """Return the compiler option to add 'dir' to the list of - directories searched for runtime libraries. - """ - raise NotImplementedError - - def library_option(self, lib): - """Return the compiler option to add 'dir' to the list of libraries - linked into the shared library or executable. - """ - raise NotImplementedError - - def has_function(self, 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. - """ - - # this can't be included at module scope because it tries to - # import math which might not be available at that point - maybe - # the necessary logic should just be inlined? - import tempfile - if includes is None: - includes = [] - if include_dirs is None: - include_dirs = [] - if libraries is None: - libraries = [] - if library_dirs is None: - library_dirs = [] - fd, fname = tempfile.mkstemp(".c", funcname, text=True) - with os.fdopen(fd, "w") as f: - for incl in includes: - f.write("""#include "%s"\n""" % incl) - f.write("""\ -main (int argc, char **argv) { - %s(); -} -""" % funcname) - try: - objects = self.compile([fname], include_dirs=include_dirs) - except CompileError: - return False - - try: - self.link_executable(objects, "a.out", - libraries=libraries, - library_dirs=library_dirs) - except (LinkError, TypeError): - return False - return True - - def find_library_file(self, dirs, lib, debug=False): - """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. - """ - raise NotImplementedError - - # -- Filename generation methods ----------------------------------- - - # The default implementation of the filename generating methods are - # prejudiced towards the Unix/DOS/Windows view of the world: - # * object files are named by replacing the source file extension - # (eg. .c/.cpp -> .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 95526676ff..0000000000 --- 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 66f6e9a6bb..0000000000 --- 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 82659fe478..0000000000 --- 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 39a10b277c..0000000000 --- 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 3458faabe0..0000000000 --- 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 ab026a83b6..0000000000 --- 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 3d45ca9f90..0000000000 --- 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 e028dc55fb..0000000000 --- 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 d633b636ad..0000000000 --- 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 607767e971..0000000000 --- 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 8878129609..0000000000 --- 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 61dd5fc58e..0000000000 --- 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 776ba4014c..0000000000 --- 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 40e733013a..0000000000 --- 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