From b1b8cd3a3b5d47bfd31922cdca9118bd88c9e748 Mon Sep 17 00:00:00 2001 From: lambdaclan <47409392+lambdaclan@users.noreply.github.com> Date: Mon, 1 Apr 2019 16:20:31 +0900 Subject: [PATCH] Initial commit (fork) --- .gitignore | 13 + .travis.yml | 14 + CHANGELOG.md | 266 ++ CONTRIBUTING.md | 23 + COPYING.LESSER | 166 + INSTALL.md | 3 + MANIFEST.in | 5 + README.md | 192 + RELEASE.md | 44 + bin/_rez-complete | 3 + bin/_rez_fwd | 3 + bin/bez | 3 + bin/rez | 3 + bin/rez-bind | 3 + bin/rez-build | 3 + bin/rez-config | 3 + bin/rez-context | 3 + bin/rez-cp | 3 + bin/rez-depends | 3 + bin/rez-diff | 3 + bin/rez-env | 3 + bin/rez-gui | 3 + bin/rez-help | 3 + bin/rez-interpret | 3 + bin/rez-memcache | 3 + bin/rez-pip | 3 + bin/rez-plugins | 3 + bin/rez-python | 3 + bin/rez-release | 3 + bin/rez-search | 3 + bin/rez-selftest | 3 + bin/rez-status | 3 + bin/rez-suite | 3 + bin/rez-test | 3 + bin/rez-view | 3 + bin/rez-yaml2py | 3 + bin/rezolve | 3 + docs/Makefile | 177 + docs/api/modules.rst | 8 + docs/api/rez._sys.rst | 22 + docs/api/rez.bind.rst | 62 + docs/api/rez.cli.rst | 134 + docs/api/rez.rst | 256 ++ docs/api/rez.tests.rst | 78 + docs/api/rezplugins.build_system.rst | 38 + docs/api/rezplugins.release_hook.rst | 22 + docs/api/rezplugins.release_vcs.rst | 38 + docs/api/rezplugins.rst | 21 + docs/api/rezplugins.shell.rst | 54 + docs/api/rezplugins.source_retriever.rst | 38 + docs/conf.py | 274 ++ docs/index.rst | 152 + docs/make.bat | 242 ++ docs/one-liners.rst | 71 + example_packages/hello_world/CMakeLists.txt | 17 + example_packages/hello_world/README.md | 15 + example_packages/hello_world/bin/hello | 4 + example_packages/hello_world/package.py | 26 + .../hello_world/python/hello_world.py | 4 + .../hello_world/rezbuild.py.example | 46 + install.py | 182 + media/rez_banner.svg | 96 + media/rez_banner_256.png | Bin 0 -> 21733 bytes media/rez_logo.svg | 82 + media/rez_logo.xcf | Bin 0 -> 335130 bytes media/rez_logo_256.png | Bin 0 -> 15952 bytes media/rez_logo_32.png | Bin 0 -> 1830 bytes release_util.py | 144 + setup.py | 116 + src/build_utils/__init__.py | 0 src/build_utils/add_license.py | 115 + src/build_utils/distlib/__init__.py | 23 + src/build_utils/distlib/_backport/__init__.py | 6 + src/build_utils/distlib/_backport/misc.py | 41 + src/build_utils/distlib/_backport/shutil.py | 761 ++++ .../distlib/_backport/sysconfig.cfg | 84 + .../distlib/_backport/sysconfig.py | 788 ++++ src/build_utils/distlib/_backport/tarfile.py | 2607 ++++++++++++ src/build_utils/distlib/compat.py | 1102 +++++ src/build_utils/distlib/database.py | 1301 ++++++ src/build_utils/distlib/index.py | 513 +++ src/build_utils/distlib/locators.py | 1195 ++++++ src/build_utils/distlib/manifest.py | 364 ++ src/build_utils/distlib/markers.py | 190 + src/build_utils/distlib/metadata.py | 1058 +++++ src/build_utils/distlib/resources.py | 323 ++ src/build_utils/distlib/scripts.py | 335 ++ src/build_utils/distlib/t32.exe | Bin 0 -> 91648 bytes src/build_utils/distlib/t64.exe | Bin 0 -> 95232 bytes src/build_utils/distlib/util.py | 1575 +++++++ src/build_utils/distlib/version.py | 721 ++++ src/build_utils/distlib/w32.exe | Bin 0 -> 88576 bytes src/build_utils/distlib/w64.exe | Bin 0 -> 92160 bytes src/build_utils/distlib/wheel.py | 976 +++++ src/build_utils/make_README.py | 85 + src/build_utils/virtualenv/LICENSE.txt | 22 + src/build_utils/virtualenv/__init__.py | 0 src/build_utils/virtualenv/virtualenv.py | 2342 +++++++++++ .../pip-1.5.6-py2.py3-none-any.whl | Bin 0 -> 1002021 bytes .../setuptools-3.6-py2.py3-none-any.whl | Bin 0 -> 547415 bytes src/rez/SOLVER.md | 252 ++ src/rez/__init__.py | 51 + src/rez/backport/__init__.py | 0 src/rez/backport/importlib.py | 37 + src/rez/backport/lru_cache.py | 136 + src/rez/backport/ordereddict.py | 127 + src/rez/backport/shutilwhich.py | 62 + src/rez/backport/zipfile.py | 1411 +++++++ src/rez/bind/PyQt.py | 28 + src/rez/bind/PySide.py | 18 + src/rez/bind/__init__.py | 16 + src/rez/bind/_pymodule.py | 118 + src/rez/bind/_utils.py | 139 + src/rez/bind/arch.py | 34 + src/rez/bind/cmake.py | 55 + src/rez/bind/hello_world.py | 68 + src/rez/bind/os.py | 36 + src/rez/bind/pip.py | 18 + src/rez/bind/platform.py | 34 + src/rez/bind/python.py | 104 + src/rez/bind/rez.py | 53 + src/rez/bind/rezgui.py | 82 + src/rez/bind/setuptools.py | 26 + src/rez/bind/sip.py | 23 + src/rez/build_process_.py | 442 ++ src/rez/build_system.py | 269 ++ src/rez/cli/__init__.py | 16 + src/rez/cli/_bez.py | 96 + src/rez/cli/_complete_util.py | 156 + src/rez/cli/_main.py | 171 + src/rez/cli/_util.py | 178 + src/rez/cli/bind.py | 119 + src/rez/cli/build.py | 183 + src/rez/cli/complete.py | 113 + src/rez/cli/config.py | 67 + src/rez/cli/context.py | 191 + src/rez/cli/cp.py | 212 + src/rez/cli/depends.py | 95 + src/rez/cli/diff.py | 47 + src/rez/cli/env.py | 267 ++ src/rez/cli/forward.py | 78 + src/rez/cli/gui.py | 37 + src/rez/cli/help.py | 75 + src/rez/cli/interpret.py | 91 + src/rez/cli/memcache.py | 181 + src/rez/cli/pip.py | 88 + src/rez/cli/plugins.py | 54 + src/rez/cli/python.py | 48 + src/rez/cli/release.py | 161 + src/rez/cli/search.py | 135 + src/rez/cli/selftest.py | 85 + src/rez/cli/status.py | 46 + src/rez/cli/suite.py | 211 + src/rez/cli/test.py | 68 + src/rez/cli/view.py | 87 + src/rez/cli/yaml2py.py | 51 + src/rez/completion/complete.csh | 8 + src/rez/completion/complete.sh | 21 + src/rez/config.py | 878 ++++ src/rez/developer_package.py | 237 ++ src/rez/exceptions.py | 217 + src/rez/package_bind.py | 188 + src/rez/package_copy.py | 399 ++ src/rez/package_filter.py | 549 +++ src/rez/package_help.py | 138 + src/rez/package_maker__.py | 232 ++ src/rez/package_order.py | 441 ++ src/rez/package_py_utils.py | 264 ++ src/rez/package_repository.py | 486 +++ src/rez/package_resources_.py | 482 +++ src/rez/package_search.py | 428 ++ src/rez/package_serialise.py | 219 + src/rez/package_test.py | 254 ++ src/rez/packages_.py | 776 ++++ src/rez/pip.py | 384 ++ src/rez/plugin_managers.py | 393 ++ src/rez/release_hook.py | 153 + src/rez/release_vcs.py | 240 ++ src/rez/resolved_context.py | 1745 ++++++++ src/rez/resolver.py | 457 ++ src/rez/rex.py | 1268 ++++++ src/rez/rex_bindings.py | 176 + src/rez/rezconfig.py | 875 ++++ src/rez/serialise.py | 434 ++ src/rez/shells.py | 419 ++ src/rez/solver.py | 2300 ++++++++++ src/rez/status.py | 388 ++ src/rez/suite.py | 770 ++++ src/rez/system.py | 306 ++ src/rez/tests/README | 45 + src/rez/tests/__init__.py | 16 + .../builds/packages/anti/1.0.0/package.py | 28 + .../builds/packages/anti/1.0.0/rezbuild.py | 33 + .../builds/packages/bah/2.1/bah/__init__.py | 0 .../data/builds/packages/bah/2.1/package.py | 27 + .../data/builds/packages/bah/2.1/rezbuild.py | 42 + .../build_util/1/build_util/__init__.py | 50 + .../builds/packages/build_util/1/package.py | 24 + .../builds/packages/build_util/1/rezbuild.py | 40 + .../builds/packages/floob/floob/__init__.py | 19 + .../data/builds/packages/floob/package.py | 30 + .../data/builds/packages/floob/rezbuild.py | 30 + .../builds/packages/foo/1.0.0/foo/__init__.py | 22 + .../data/builds/packages/foo/1.0.0/package.py | 28 + .../builds/packages/foo/1.0.0/rezbuild.py | 36 + .../builds/packages/foo/1.1.0/foo/__init__.py | 22 + .../data/builds/packages/foo/1.1.0/package.py | 32 + .../builds/packages/foo/1.1.0/rezbuild.py | 36 + .../data/builds/packages/hello/1.0/Makefile | 8 + .../builds/packages/hello/1.0/lib/main.cpp | 8 + .../data/builds/packages/hello/1.0/package.py | 13 + .../data/builds/packages/loco/3/package.py | 24 + .../data/builds/packages/loco/3/rezbuild.py | 20 + .../packages/sup_world/3.8/CMakeLists.txt | 6 + .../packages/sup_world/3.8/lib/CMakeLists.txt | 19 + .../sup_world/3.8/lib/ghetto_greet.cpp | 13 + .../packages/sup_world/3.8/lib/ghetto_greet.h | 12 + .../builds/packages/sup_world/3.8/package.py | 29 + .../sup_world/3.8/util/CMakeLists.txt | 12 + .../packages/sup_world/3.8/util/main.cpp | 11 + .../translate_lib/2.2.0/CMakeLists.txt | 28 + .../packages/translate_lib/2.2.0/package.py | 35 + .../2.2.0/src/ghettoTranslator.cpp | 22 + .../2.2.0/src/ghettoTranslator.h | 28 + .../translate_lib/2.2.0/src/lolTranslator.cpp | 40 + .../translate_lib/2.2.0/src/lolTranslator.h | 33 + .../translate_lib/2.2.0/src/translator.cpp | 25 + .../translate_lib/2.2.0/src/translator.h | 34 + .../data/builds/packages/whack/package.py | 21 + .../data/builds/packages/whack/rezbuild.py | 19 + .../packages/rextest/1.1/package.yaml | 10 + .../packages/rextest/1.2/package.yaml | 10 + .../commands/packages/rextest/1.3/package.py | 26 + .../commands/packages/rextest2/2/package.py | 26 + src/rez/tests/data/config/package.py | 38 + src/rez/tests/data/config/test1.yaml | 8 + src/rez/tests/data/config/test2.py | 10 + .../data/packages/developer/package.yaml | 17 + .../packages/developer_changed/package.yaml | 17 + .../packages/developer_dynamic/package.py | 20 + .../packages/developer_novar/package.yaml | 13 + .../developer_novar_changed/package.yaml | 13 + .../py_packages/late_binding/1.0/package.py | 26 + .../tests/data/packages/py_packages/multi.py | 27 + .../py_packages/single_unversioned.py | 17 + .../packages/py_packages/single_versioned.py | 18 + .../py_packages/timestamped/1.0.5/package.py | 5 + .../py_packages/timestamped/1.0.6/package.py | 5 + .../py_packages/timestamped/1.1.0/package.py | 5 + .../py_packages/timestamped/1.1.1/package.py | 5 + .../py_packages/timestamped/1.2.0/package.py | 5 + .../py_packages/timestamped/2.0.0/package.py | 5 + .../py_packages/timestamped/2.1.0/package.py | 5 + .../py_packages/timestamped/2.1.5/package.py | 5 + .../py_packages/unversioned_py/package.py | 17 + .../py_packages/variants_py/2.0/package.py | 31 + .../py_packages/versioned/2.0/package.py | 19 + .../py_packages/versioned/3.0/package.py | 21 + .../data/packages/yaml_packages/multi.yaml | 15 + .../yaml_packages/single_unversioned.yaml | 1 + .../yaml_packages/single_versioned.yaml | 4 + .../yaml_packages/unversioned/package.yaml | 1 + .../yaml_packages/versioned/1.0/package.yaml | 17 + .../yaml_packages/versioned/2.0/package.yaml | 5 + .../tests/data/python/early_bind/__init__.py | 0 .../data/python/early_bind/early_utils.py | 6 + .../tests/data/python/late_bind/late_utils.py | 4 + src/rez/tests/data/release/data/data.txt | 3 + src/rez/tests/data/release/package.yaml | 6 + src/rez/tests/data/release/rezbuild.py | 40 + .../tests/data/release/variants/package.yaml | 8 + .../tests/data/release/variants/rezbuild.py | 6 + .../release/variants/spangle/1.0/package.yaml | 5 + .../release/variants/spangle/1.1/package.yaml | 5 + .../release/variants/spangle/2.0/package.yaml | 5 + src/rez/tests/data/solver/packages/README | 171 + .../data/solver/packages/bahish/1/package.py | 20 + .../data/solver/packages/bahish/2/package.py | 20 + .../data/solver/packages/nada/package.py | 17 + .../data/solver/packages/nopy/2.1/package.py | 20 + .../data/solver/packages/pybah/4/package.py | 20 + .../data/solver/packages/pybah/5/package.py | 20 + .../data/solver/packages/pydad/1/package.py | 20 + .../data/solver/packages/pydad/2/package.py | 20 + .../data/solver/packages/pydad/3/package.py | 20 + .../solver/packages/pyfoo/3.0.0/package.py | 20 + .../solver/packages/pyfoo/3.1.0/package.py | 20 + .../data/solver/packages/pymum/1/package.py | 20 + .../data/solver/packages/pymum/2/package.py | 20 + .../data/solver/packages/pymum/3/package.py | 20 + .../data/solver/packages/pyodd/1/package.py | 20 + .../data/solver/packages/pyodd/2/package.py | 20 + .../data/solver/packages/pyson/1/package.py | 20 + .../data/solver/packages/pyson/2/package.py | 20 + .../data/solver/packages/pysplit/5/package.py | 18 + .../data/solver/packages/pysplit/6/package.py | 20 + .../data/solver/packages/pysplit/7/package.py | 20 + .../solver/packages/python/2.5.2/package.py | 18 + .../solver/packages/python/2.6.0/package.py | 18 + .../solver/packages/python/2.6.8/package.py | 18 + .../solver/packages/python/2.7.0/package.py | 18 + .../solver/packages/pyvariants/2/package.py | 4 + .../test_variant_split_end/1.0/package.py | 5 + .../test_variant_split_end/2.0/package.py | 4 + .../test_variant_split_end/3.0/package.py | 4 + .../test_variant_split_end/4.0/package.py | 4 + .../test_variant_split_mid1/1.0/package.py | 4 + .../test_variant_split_mid1/2.0/package.py | 4 + .../test_variant_split_mid2/1.0/package.py | 4 + .../test_variant_split_mid2/2.0/package.py | 4 + .../test_variant_split_start/1.0/package.py | 4 + .../test_variant_split_start/2.0/package.py | 4 + .../data/suites/packages/bah/package.yaml | 8 + .../data/suites/packages/eek/package.yaml | 4 + .../data/suites/packages/foo/package.yaml | 4 + src/rez/tests/test_build.py | 193 + src/rez/tests/test_commands.py | 168 + src/rez/tests/test_completion.py | 79 + src/rez/tests/test_config.py | 247 ++ src/rez/tests/test_context.py | 112 + src/rez/tests/test_copy_package.py | 328 ++ src/rez/tests/test_formatter.py | 294 ++ src/rez/tests/test_imports.py | 71 + src/rez/tests/test_logging.py | 25 + src/rez/tests/test_packages.py | 405 ++ src/rez/tests/test_release.py | 252 ++ src/rez/tests/test_resources_.py | 317 ++ src/rez/tests/test_rex.py | 428 ++ src/rez/tests/test_schema.py | 25 + src/rez/tests/test_shells.py | 347 ++ src/rez/tests/test_solver.py | 239 ++ src/rez/tests/test_suites.py | 156 + src/rez/tests/test_version.py | 29 + src/rez/tests/util.py | 329 ++ src/rez/util.py | 191 + src/rez/utils/__init__.py | 33 + src/rez/utils/_version.py | 27 + src/rez/utils/amqp.py | 120 + src/rez/utils/backcompat.py | 182 + src/rez/utils/colorize.py | 322 ++ src/rez/utils/data_utils.py | 598 +++ src/rez/utils/diff_packages.py | 65 + src/rez/utils/filesystem.py | 573 +++ src/rez/utils/formatting.py | 504 +++ src/rez/utils/graph_utils.py | 285 ++ src/rez/utils/json.py | 36 + src/rez/utils/lint_helper.py | 38 + src/rez/utils/logging.conf | 26 + src/rez/utils/logging_.py | 86 + src/rez/utils/memcached.py | 396 ++ src/rez/utils/patching.py | 94 + src/rez/utils/platform_.py | 572 +++ src/rez/utils/platform_mapped.py | 42 + src/rez/utils/py_dist.py | 261 ++ src/rez/utils/resources.py | 295 ++ src/rez/utils/schema.py | 88 + src/rez/utils/scope.py | 276 ++ src/rez/utils/sourcecode.py | 346 ++ src/rez/utils/system.py | 38 + src/rez/utils/yaml.py | 92 + src/rez/vendor/__init__.py | 0 src/rez/vendor/amqp/LICENSE | 458 ++ src/rez/vendor/amqp/__init__.py | 70 + src/rez/vendor/amqp/abstract_channel.py | 93 + src/rez/vendor/amqp/basic_message.py | 124 + src/rez/vendor/amqp/channel.py | 2550 ++++++++++++ src/rez/vendor/amqp/connection.py | 1008 +++++ src/rez/vendor/amqp/exceptions.py | 262 ++ src/rez/vendor/amqp/five.py | 191 + src/rez/vendor/amqp/method_framing.py | 231 ++ src/rez/vendor/amqp/protocol.py | 13 + src/rez/vendor/amqp/serialization.py | 509 +++ src/rez/vendor/amqp/transport.py | 299 ++ src/rez/vendor/amqp/utils.py | 102 + src/rez/vendor/argcomplete/LICENSE.rst | 177 + src/rez/vendor/argcomplete/__init__.py | 449 ++ src/rez/vendor/argcomplete/completers.py | 79 + src/rez/vendor/argcomplete/my_argparse.py | 285 ++ src/rez/vendor/argcomplete/my_shlex.py | 300 ++ src/rez/vendor/argparse.py | 2362 +++++++++++ src/rez/vendor/atomicwrites/LICENSE | 19 + src/rez/vendor/atomicwrites/__init__.py | 206 + src/rez/vendor/colorama/__init__.py | 7 + src/rez/vendor/colorama/ansi.py | 50 + src/rez/vendor/colorama/ansitowin32.py | 190 + src/rez/vendor/colorama/initialise.py | 63 + src/rez/vendor/colorama/win32.py | 137 + src/rez/vendor/colorama/winterm.py | 120 + src/rez/vendor/distlib/__init__.py | 23 + src/rez/vendor/distlib/_backport/__init__.py | 6 + src/rez/vendor/distlib/_backport/misc.py | 41 + src/rez/vendor/distlib/_backport/shutil.py | 761 ++++ .../vendor/distlib/_backport/sysconfig.cfg | 84 + src/rez/vendor/distlib/_backport/sysconfig.py | 788 ++++ src/rez/vendor/distlib/_backport/tarfile.py | 2607 ++++++++++++ src/rez/vendor/distlib/compat.py | 1111 +++++ src/rez/vendor/distlib/database.py | 1312 ++++++ src/rez/vendor/distlib/index.py | 513 +++ src/rez/vendor/distlib/locators.py | 1257 ++++++ src/rez/vendor/distlib/manifest.py | 367 ++ src/rez/vendor/distlib/markers.py | 190 + src/rez/vendor/distlib/metadata.py | 1066 +++++ src/rez/vendor/distlib/resources.py | 350 ++ src/rez/vendor/distlib/scripts.py | 384 ++ src/rez/vendor/distlib/t32.exe | Bin 0 -> 89088 bytes src/rez/vendor/distlib/t64.exe | Bin 0 -> 97792 bytes src/rez/vendor/distlib/util.py | 1609 +++++++ src/rez/vendor/distlib/version.py | 742 ++++ src/rez/vendor/distlib/w32.exe | Bin 0 -> 85504 bytes src/rez/vendor/distlib/w64.exe | Bin 0 -> 94208 bytes src/rez/vendor/distlib/wheel.py | 978 +++++ src/rez/vendor/enum/LICENSE | 32 + src/rez/vendor/enum/__init__.py | 767 ++++ src/rez/vendor/enum/doc/enum.rst | 725 ++++ src/rez/vendor/enum/enum.py | 767 ++++ src/rez/vendor/lockfile/LICENSE | 21 + src/rez/vendor/lockfile/RELEASE-NOTES | 50 + src/rez/vendor/lockfile/__init__.py | 335 ++ src/rez/vendor/lockfile/linklockfile.py | 73 + src/rez/vendor/lockfile/mkdirlockfile.py | 85 + src/rez/vendor/lockfile/pidlockfile.py | 193 + src/rez/vendor/lockfile/sqlitelockfile.py | 155 + src/rez/vendor/lockfile/symlinklockfile.py | 69 + src/rez/vendor/memcache/__init__.py | 0 src/rez/vendor/memcache/memcache.py | 1418 +++++++ src/rez/vendor/progress/LICENSE | 13 + src/rez/vendor/progress/__init__.py | 123 + src/rez/vendor/progress/bar.py | 83 + src/rez/vendor/progress/counter.py | 47 + src/rez/vendor/progress/helpers.py | 91 + src/rez/vendor/progress/spinner.py | 40 + src/rez/vendor/pydot/LICENSE | 16 + src/rez/vendor/pydot/__init__.py | 0 src/rez/vendor/pydot/dot_parser.py | 533 +++ src/rez/vendor/pydot/pydot.py | 2035 +++++++++ src/rez/vendor/pygraph/COPYING | 32 + src/rez/vendor/pygraph/__init__.py | 63 + src/rez/vendor/pygraph/algorithms/__init__.py | 31 + .../pygraph/algorithms/accessibility.py | 347 ++ src/rez/vendor/pygraph/algorithms/critical.py | 163 + src/rez/vendor/pygraph/algorithms/cycles.py | 108 + .../pygraph/algorithms/filters/__init__.py | 29 + .../vendor/pygraph/algorithms/filters/find.py | 78 + .../vendor/pygraph/algorithms/filters/null.py | 68 + .../pygraph/algorithms/filters/radius.py | 96 + .../vendor/pygraph/algorithms/generators.py | 132 + .../pygraph/algorithms/heuristics/__init__.py | 32 + .../pygraph/algorithms/heuristics/chow.py | 77 + .../algorithms/heuristics/euclidean.py | 97 + src/rez/vendor/pygraph/algorithms/minmax.py | 516 +++ src/rez/vendor/pygraph/algorithms/pagerank.py | 76 + .../vendor/pygraph/algorithms/searching.py | 153 + src/rez/vendor/pygraph/algorithms/sorting.py | 51 + .../vendor/pygraph/algorithms/traversal.py | 84 + src/rez/vendor/pygraph/algorithms/utils.py | 89 + src/rez/vendor/pygraph/classes/__init__.py | 27 + src/rez/vendor/pygraph/classes/digraph.py | 259 ++ src/rez/vendor/pygraph/classes/exceptions.py | 76 + src/rez/vendor/pygraph/classes/graph.py | 230 + src/rez/vendor/pygraph/classes/hypergraph.py | 363 ++ src/rez/vendor/pygraph/mixins/__init__.py | 33 + src/rez/vendor/pygraph/mixins/basegraph.py | 32 + src/rez/vendor/pygraph/mixins/common.py | 215 + src/rez/vendor/pygraph/mixins/labeling.py | 227 + src/rez/vendor/pygraph/readwrite/__init__.py | 31 + src/rez/vendor/pygraph/readwrite/dot.py | 263 ++ src/rez/vendor/pygraph/readwrite/markup.py | 195 + src/rez/vendor/pyparsing/LICENSE | 18 + src/rez/vendor/pyparsing/__init__.py | 0 src/rez/vendor/pyparsing/pyparsing.py | 3688 +++++++++++++++++ src/rez/vendor/schema/LICENSE-MIT | 19 + src/rez/vendor/schema/__init__.py | 0 src/rez/vendor/schema/schema.py | 274 ++ src/rez/vendor/schema/test_schema.py | 376 ++ src/rez/vendor/six/CHANGES | 231 ++ src/rez/vendor/six/LICENSE | 18 + src/rez/vendor/six/README | 16 + src/rez/vendor/six/__init__.py | 0 src/rez/vendor/six/six.py | 762 ++++ src/rez/vendor/sortedcontainers/LICENSE | 13 + src/rez/vendor/sortedcontainers/__init__.py | 52 + src/rez/vendor/sortedcontainers/sorteddict.py | 741 ++++ src/rez/vendor/sortedcontainers/sortedlist.py | 2492 +++++++++++ src/rez/vendor/sortedcontainers/sortedset.py | 327 ++ src/rez/vendor/unittest2/__init__.py | 68 + src/rez/vendor/unittest2/__main__.py | 10 + src/rez/vendor/unittest2/case.py | 1084 +++++ src/rez/vendor/unittest2/collector.py | 9 + src/rez/vendor/unittest2/compatibility.py | 64 + src/rez/vendor/unittest2/loader.py | 322 ++ src/rez/vendor/unittest2/main.py | 241 ++ src/rez/vendor/unittest2/result.py | 183 + src/rez/vendor/unittest2/runner.py | 206 + src/rez/vendor/unittest2/signals.py | 57 + src/rez/vendor/unittest2/suite.py | 287 ++ src/rez/vendor/unittest2/util.py | 99 + src/rez/vendor/version/__init__.py | 0 src/rez/vendor/version/requirement.py | 400 ++ src/rez/vendor/version/test.py | 492 +++ src/rez/vendor/version/util.py | 100 + src/rez/vendor/version/version.py | 1393 +++++++ src/rez/vendor/yaml/LICENSE | 19 + src/rez/vendor/yaml/__init__.py | 315 ++ src/rez/vendor/yaml/composer.py | 139 + src/rez/vendor/yaml/constructor.py | 675 +++ src/rez/vendor/yaml/cyaml.py | 85 + src/rez/vendor/yaml/dumper.py | 62 + src/rez/vendor/yaml/emitter.py | 1140 +++++ src/rez/vendor/yaml/error.py | 75 + src/rez/vendor/yaml/events.py | 86 + src/rez/vendor/yaml/loader.py | 40 + src/rez/vendor/yaml/nodes.py | 49 + src/rez/vendor/yaml/parser.py | 589 +++ src/rez/vendor/yaml/reader.py | 190 + src/rez/vendor/yaml/representer.py | 484 +++ src/rez/vendor/yaml/resolver.py | 224 + src/rez/vendor/yaml/scanner.py | 1457 +++++++ src/rez/vendor/yaml/serializer.py | 111 + src/rez/vendor/yaml/tokens.py | 104 + src/rez/wrapper.py | 285 ++ src/rezgui/__init__.py | 18 + src/rezgui/app.py | 61 + src/rezgui/dialogs/AboutDialog.py | 53 + src/rezgui/dialogs/BrowsePackageDialog.py | 75 + src/rezgui/dialogs/ImageViewerDialog.py | 40 + src/rezgui/dialogs/ProcessDialog.py | 103 + src/rezgui/dialogs/ResolveDialog.py | 344 ++ src/rezgui/dialogs/VariantVersionsDialog.py | 38 + src/rezgui/dialogs/WriteGraphDialog.py | 149 + src/rezgui/dialogs/__init__.py | 16 + src/rezgui/icons/advanced_resolve.png | Bin 0 -> 951 bytes src/rezgui/icons/changelog.png | Bin 0 -> 777 bytes src/rezgui/icons/clock.png | Bin 0 -> 763 bytes src/rezgui/icons/clock_warning.png | Bin 0 -> 1094 bytes src/rezgui/icons/cog.png | Bin 0 -> 431 bytes src/rezgui/icons/context.png | Bin 0 -> 895 bytes src/rezgui/icons/context_settings.png | Bin 0 -> 997 bytes src/rezgui/icons/depends.png | Bin 0 -> 248 bytes src/rezgui/icons/diff.png | Bin 0 -> 883 bytes src/rezgui/icons/diff_to_disk.png | Bin 0 -> 822 bytes src/rezgui/icons/diff_to_other.png | Bin 0 -> 850 bytes src/rezgui/icons/equal_to.png | Bin 0 -> 929 bytes src/rezgui/icons/equalish.png | Bin 0 -> 975 bytes src/rezgui/icons/error.png | Bin 0 -> 888 bytes src/rezgui/icons/excluded.png | Bin 0 -> 1028 bytes src/rezgui/icons/find.png | Bin 0 -> 879 bytes src/rezgui/icons/github_32.png | Bin 0 -> 938 bytes src/rezgui/icons/graph.png | Bin 0 -> 839 bytes src/rezgui/icons/greater_than.png | Bin 0 -> 1035 bytes src/rezgui/icons/greater_than_1.png | Bin 0 -> 1036 bytes src/rezgui/icons/greater_than_2.png | Bin 0 -> 1061 bytes src/rezgui/icons/greater_than_3.png | Bin 0 -> 1061 bytes src/rezgui/icons/green_tick.png | Bin 0 -> 615 bytes src/rezgui/icons/green_white_tick.png | Bin 0 -> 767 bytes src/rezgui/icons/help.png | Bin 0 -> 935 bytes src/rezgui/icons/info.png | Bin 0 -> 905 bytes src/rezgui/icons/less_than.png | Bin 0 -> 1001 bytes src/rezgui/icons/less_than_1.png | Bin 0 -> 997 bytes src/rezgui/icons/less_than_2.png | Bin 0 -> 1025 bytes src/rezgui/icons/less_than_3.png | Bin 0 -> 1028 bytes src/rezgui/icons/local.png | Bin 0 -> 929 bytes src/rezgui/icons/lock.png | Bin 0 -> 342 bytes src/rezgui/icons/lock_2.png | Bin 0 -> 438 bytes src/rezgui/icons/lock_2_faint.png | Bin 0 -> 394 bytes src/rezgui/icons/lock_3.png | Bin 0 -> 443 bytes src/rezgui/icons/lock_3_faint.png | Bin 0 -> 401 bytes src/rezgui/icons/lock_4.png | Bin 0 -> 445 bytes src/rezgui/icons/lock_4_faint.png | Bin 0 -> 397 bytes src/rezgui/icons/lock_faint.png | Bin 0 -> 321 bytes src/rezgui/icons/missing.png | Bin 0 -> 944 bytes src/rezgui/icons/new.png | Bin 0 -> 829 bytes src/rezgui/icons/no_lock.png | Bin 0 -> 619 bytes src/rezgui/icons/no_lock_faint.png | Bin 0 -> 554 bytes src/rezgui/icons/old_man.png | Bin 0 -> 1174 bytes src/rezgui/icons/package.png | Bin 0 -> 721 bytes src/rezgui/icons/pink.png | Bin 0 -> 165 bytes src/rezgui/icons/resolve.png | Bin 0 -> 869 bytes src/rezgui/icons/revert.png | Bin 0 -> 865 bytes src/rezgui/icons/revert_to_diff.png | Bin 0 -> 792 bytes src/rezgui/icons/revert_to_disk.png | Bin 0 -> 900 bytes src/rezgui/icons/spanner.png | Bin 0 -> 377 bytes src/rezgui/icons/terminal.png | Bin 0 -> 856 bytes src/rezgui/icons/time_lock.png | Bin 0 -> 460 bytes src/rezgui/icons/tools.png | Bin 0 -> 382 bytes src/rezgui/icons/variant.png | Bin 0 -> 509 bytes src/rezgui/icons/versions.png | Bin 0 -> 797 bytes src/rezgui/icons/warning.png | Bin 0 -> 837 bytes src/rezgui/icons/yellow_tick.png | Bin 0 -> 607 bytes src/rezgui/icons/yellow_white_tick.png | Bin 0 -> 767 bytes src/rezgui/mixins/ContextViewMixin.py | 51 + src/rezgui/mixins/StoreSizeMixin.py | 37 + src/rezgui/mixins/__init__.py | 16 + src/rezgui/models/ContextModel.py | 269 ++ src/rezgui/models/__init__.py | 16 + src/rezgui/objects/App.py | 106 + src/rezgui/objects/Config.py | 145 + src/rezgui/objects/LoadPackagesThread.py | 59 + src/rezgui/objects/ProcessTrackerThread.py | 113 + src/rezgui/objects/ResolveThread.py | 71 + src/rezgui/objects/__init__.py | 16 + src/rezgui/qt.py | 63 + src/rezgui/rezguiconfig | 49 + src/rezgui/util.py | 166 + src/rezgui/widgets/BrowsePackagePane.py | 43 + src/rezgui/widgets/BrowsePackageWidget.py | 86 + src/rezgui/widgets/ChangelogEdit.py | 70 + src/rezgui/widgets/ConfiguredSplitter.py | 48 + src/rezgui/widgets/ContextDetailsWidget.py | 114 + src/rezgui/widgets/ContextEnvironTable.py | 88 + src/rezgui/widgets/ContextEnvironWidget.py | 56 + src/rezgui/widgets/ContextManagerWidget.py | 375 ++ src/rezgui/widgets/ContextResolveTimeLabel.py | 52 + src/rezgui/widgets/ContextSettingsWidget.py | 181 + src/rezgui/widgets/ContextTableWidget.py | 742 ++++ src/rezgui/widgets/ContextToolsWidget.py | 110 + .../widgets/EffectivePackageCellWidget.py | 39 + src/rezgui/widgets/FindPopup.py | 67 + src/rezgui/widgets/IconButton.py | 34 + src/rezgui/widgets/ImageViewerWidget.py | 121 + src/rezgui/widgets/PackageLineEdit.py | 167 + src/rezgui/widgets/PackageLoadingWidget.py | 137 + src/rezgui/widgets/PackageSelectWidget.py | 90 + src/rezgui/widgets/PackageTabWidget.py | 145 + src/rezgui/widgets/PackageVersionsTable.py | 147 + src/rezgui/widgets/SearchableTextEdit.py | 51 + src/rezgui/widgets/StreamableTextEdit.py | 68 + src/rezgui/widgets/TimeSelecterPopup.py | 144 + src/rezgui/widgets/TimestampWidget.py | 93 + src/rezgui/widgets/ToolWidget.py | 126 + src/rezgui/widgets/VariantCellWidget.py | 330 ++ src/rezgui/widgets/VariantDetailsWidget.py | 62 + src/rezgui/widgets/VariantHelpWidget.py | 153 + src/rezgui/widgets/VariantSummaryWidget.py | 116 + src/rezgui/widgets/VariantToolsList.py | 83 + src/rezgui/widgets/VariantVersionsTable.py | 180 + src/rezgui/widgets/VariantVersionsWidget.py | 190 + src/rezgui/widgets/VariantsList.py | 71 + src/rezgui/widgets/ViewGraphButton.py | 74 + src/rezgui/widgets/__init__.py | 16 + src/rezgui/windows/BrowsePackageSubWindow.py | 36 + src/rezgui/windows/ContextSubWindow.py | 162 + src/rezgui/windows/MainWindow.py | 175 + src/rezgui/windows/__init__.py | 16 + src/rezplugins/__init__.py | 16 + src/rezplugins/build_process/__init__.py | 18 + src/rezplugins/build_process/local.py | 280 ++ src/rezplugins/build_process/remote.py | 41 + src/rezplugins/build_process/rezconfig | 0 src/rezplugins/build_system/__init__.py | 18 + src/rezplugins/build_system/bez.py | 152 + src/rezplugins/build_system/cmake.py | 279 ++ .../build_system/cmake_files/Colorize.cmake | 63 + .../cmake_files/FindStaticLibs.cmake | 49 + .../cmake_files/InstallDirs.cmake | 71 + .../cmake_files/InstallFiles.cmake | 145 + .../cmake_files/InstallPython.cmake | 128 + .../build_system/cmake_files/RezBuild.cmake | 115 + .../cmake_files/RezFindPackages.cmake | 253 ++ .../cmake_files/RezInstallCMake.cmake | 264 ++ .../cmake_files/RezInstallContext.cmake | 111 + .../cmake_files/RezInstallDoxygen.cmake | 168 + .../cmake_files/RezInstallPython.cmake | 40 + .../cmake_files/RezPipInstall.cmake | 120 + .../build_system/cmake_files/RezProject.cmake | 20 + .../cmake_files/RezRepository.cmake | 32 + .../build_system/cmake_files/Utils.cmake | 159 + src/rezplugins/build_system/custom.py | 213 + src/rezplugins/build_system/make.py | 40 + src/rezplugins/build_system/rezconfig | 21 + src/rezplugins/build_system/rezconfig.py | 49 + .../build_system/template_files/Doxyfile | 1808 ++++++++ src/rezplugins/package_repository/__init__.py | 18 + .../package_repository/filesystem.py | 1135 +++++ src/rezplugins/package_repository/memory.py | 213 + src/rezplugins/package_repository/rezconfig | 37 + src/rezplugins/release_hook/__init__.py | 18 + src/rezplugins/release_hook/amqp.py | 112 + src/rezplugins/release_hook/command.py | 215 + .../emailer-recipients-example.yaml | 43 + src/rezplugins/release_hook/emailer.py | 168 + src/rezplugins/release_hook/rezconfig | 125 + src/rezplugins/release_vcs/__init__.py | 18 + src/rezplugins/release_vcs/git.py | 252 ++ src/rezplugins/release_vcs/hg.py | 289 ++ src/rezplugins/release_vcs/rezconfig | 3 + src/rezplugins/release_vcs/stub.py | 92 + src/rezplugins/release_vcs/svn.py | 150 + src/rezplugins/shell/__init__.py | 18 + src/rezplugins/shell/bash.py | 109 + src/rezplugins/shell/cmd.py | 332 ++ src/rezplugins/shell/csh.py | 171 + src/rezplugins/shell/rezconfig | 14 + src/rezplugins/shell/sh.py | 164 + src/rezplugins/shell/tcsh.py | 68 + src/support/README.md | 3 + src/support/package_utils/README | 4 + src/support/package_utils/get_committers.sh | 16 + src/support/package_utils/set_authors.py | 21 + src/support/shotgun_toolkit/rez_app_launch.py | 171 + tag.sh | 34 + tests/__init__.py | 0 tests/test.py | 17 + tox.ini | 6 + wiki/README.md | 10 + wiki/media/icons/info.png | Bin 0 -> 1014 bytes wiki/media/icons/under_construction.png | Bin 0 -> 865 bytes wiki/media/icons/warning.png | Bin 0 -> 848 bytes wiki/media/other_pkg_mgr.png | Bin 0 -> 13425 bytes wiki/media/pkg_path_anatomy.png | Bin 0 -> 44007 bytes wiki/media/rez_banner_128.png | Bin 0 -> 9887 bytes wiki/media/rez_deps_simple_eg.png | Bin 0 -> 5589 bytes wiki/media/rez_env.png | Bin 0 -> 118567 bytes wiki/media/rez_pkg_mgr.png | Bin 0 -> 40820 bytes wiki/pages/Basic-Concepts.md | 369 ++ wiki/pages/Building-Packages.md | 249 ++ wiki/pages/Command-Line-Tools.md | 43 + wiki/pages/Contexts.md | 97 + wiki/pages/Environment-Variables.md | 84 + wiki/pages/FAQ.md | 1 + wiki/pages/Getting-Started.md | 170 + wiki/pages/Glossary.md | 50 + wiki/pages/Package-Commands.md | 475 +++ wiki/pages/Package-Definition-Guide.md | 852 ++++ wiki/pages/Suites.md | 150 + wiki/pages/Variants.md | 196 + wiki/pages/_Configuring-Rez.md | 108 + wiki/pages/_Credits.md | 16 + wiki/update-wiki.sh | 30 + 728 files changed, 130650 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 COPYING.LESSER create mode 100644 INSTALL.md create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 RELEASE.md create mode 100755 bin/_rez-complete create mode 100755 bin/_rez_fwd create mode 100755 bin/bez create mode 100755 bin/rez create mode 100755 bin/rez-bind create mode 100755 bin/rez-build create mode 100755 bin/rez-config create mode 100755 bin/rez-context create mode 100644 bin/rez-cp create mode 100755 bin/rez-depends create mode 100755 bin/rez-diff create mode 100755 bin/rez-env create mode 100755 bin/rez-gui create mode 100755 bin/rez-help create mode 100755 bin/rez-interpret create mode 100755 bin/rez-memcache create mode 100755 bin/rez-pip create mode 100755 bin/rez-plugins create mode 100755 bin/rez-python create mode 100755 bin/rez-release create mode 100755 bin/rez-search create mode 100755 bin/rez-selftest create mode 100755 bin/rez-status create mode 100755 bin/rez-suite create mode 100644 bin/rez-test create mode 100755 bin/rez-view create mode 100755 bin/rez-yaml2py create mode 100755 bin/rezolve create mode 100644 docs/Makefile create mode 100644 docs/api/modules.rst create mode 100644 docs/api/rez._sys.rst create mode 100644 docs/api/rez.bind.rst create mode 100644 docs/api/rez.cli.rst create mode 100644 docs/api/rez.rst create mode 100644 docs/api/rez.tests.rst create mode 100644 docs/api/rezplugins.build_system.rst create mode 100644 docs/api/rezplugins.release_hook.rst create mode 100644 docs/api/rezplugins.release_vcs.rst create mode 100644 docs/api/rezplugins.rst create mode 100644 docs/api/rezplugins.shell.rst create mode 100644 docs/api/rezplugins.source_retriever.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/one-liners.rst create mode 100644 example_packages/hello_world/CMakeLists.txt create mode 100644 example_packages/hello_world/README.md create mode 100644 example_packages/hello_world/bin/hello create mode 100644 example_packages/hello_world/package.py create mode 100644 example_packages/hello_world/python/hello_world.py create mode 100644 example_packages/hello_world/rezbuild.py.example create mode 100644 install.py create mode 100644 media/rez_banner.svg create mode 100644 media/rez_banner_256.png create mode 100644 media/rez_logo.svg create mode 100644 media/rez_logo.xcf create mode 100644 media/rez_logo_256.png create mode 100644 media/rez_logo_32.png create mode 100644 release_util.py create mode 100644 setup.py create mode 100644 src/build_utils/__init__.py create mode 100644 src/build_utils/add_license.py create mode 100644 src/build_utils/distlib/__init__.py create mode 100644 src/build_utils/distlib/_backport/__init__.py create mode 100644 src/build_utils/distlib/_backport/misc.py create mode 100644 src/build_utils/distlib/_backport/shutil.py create mode 100644 src/build_utils/distlib/_backport/sysconfig.cfg create mode 100644 src/build_utils/distlib/_backport/sysconfig.py create mode 100644 src/build_utils/distlib/_backport/tarfile.py create mode 100644 src/build_utils/distlib/compat.py create mode 100644 src/build_utils/distlib/database.py create mode 100644 src/build_utils/distlib/index.py create mode 100644 src/build_utils/distlib/locators.py create mode 100644 src/build_utils/distlib/manifest.py create mode 100644 src/build_utils/distlib/markers.py create mode 100644 src/build_utils/distlib/metadata.py create mode 100644 src/build_utils/distlib/resources.py create mode 100644 src/build_utils/distlib/scripts.py create mode 100644 src/build_utils/distlib/t32.exe create mode 100644 src/build_utils/distlib/t64.exe create mode 100644 src/build_utils/distlib/util.py create mode 100644 src/build_utils/distlib/version.py create mode 100644 src/build_utils/distlib/w32.exe create mode 100644 src/build_utils/distlib/w64.exe create mode 100644 src/build_utils/distlib/wheel.py create mode 100755 src/build_utils/make_README.py create mode 100644 src/build_utils/virtualenv/LICENSE.txt create mode 100644 src/build_utils/virtualenv/__init__.py create mode 100755 src/build_utils/virtualenv/virtualenv.py create mode 100644 src/build_utils/virtualenv/virtualenv_support/pip-1.5.6-py2.py3-none-any.whl create mode 100644 src/build_utils/virtualenv/virtualenv_support/setuptools-3.6-py2.py3-none-any.whl create mode 100644 src/rez/SOLVER.md create mode 100644 src/rez/__init__.py create mode 100644 src/rez/backport/__init__.py create mode 100644 src/rez/backport/importlib.py create mode 100644 src/rez/backport/lru_cache.py create mode 100644 src/rez/backport/ordereddict.py create mode 100644 src/rez/backport/shutilwhich.py create mode 100644 src/rez/backport/zipfile.py create mode 100644 src/rez/bind/PyQt.py create mode 100644 src/rez/bind/PySide.py create mode 100644 src/rez/bind/__init__.py create mode 100644 src/rez/bind/_pymodule.py create mode 100644 src/rez/bind/_utils.py create mode 100644 src/rez/bind/arch.py create mode 100644 src/rez/bind/cmake.py create mode 100644 src/rez/bind/hello_world.py create mode 100644 src/rez/bind/os.py create mode 100644 src/rez/bind/pip.py create mode 100644 src/rez/bind/platform.py create mode 100644 src/rez/bind/python.py create mode 100644 src/rez/bind/rez.py create mode 100644 src/rez/bind/rezgui.py create mode 100644 src/rez/bind/setuptools.py create mode 100644 src/rez/bind/sip.py create mode 100644 src/rez/build_process_.py create mode 100644 src/rez/build_system.py create mode 100644 src/rez/cli/__init__.py create mode 100644 src/rez/cli/_bez.py create mode 100644 src/rez/cli/_complete_util.py create mode 100644 src/rez/cli/_main.py create mode 100644 src/rez/cli/_util.py create mode 100644 src/rez/cli/bind.py create mode 100644 src/rez/cli/build.py create mode 100644 src/rez/cli/complete.py create mode 100644 src/rez/cli/config.py create mode 100644 src/rez/cli/context.py create mode 100644 src/rez/cli/cp.py create mode 100644 src/rez/cli/depends.py create mode 100644 src/rez/cli/diff.py create mode 100644 src/rez/cli/env.py create mode 100644 src/rez/cli/forward.py create mode 100644 src/rez/cli/gui.py create mode 100644 src/rez/cli/help.py create mode 100644 src/rez/cli/interpret.py create mode 100644 src/rez/cli/memcache.py create mode 100644 src/rez/cli/pip.py create mode 100644 src/rez/cli/plugins.py create mode 100644 src/rez/cli/python.py create mode 100644 src/rez/cli/release.py create mode 100644 src/rez/cli/search.py create mode 100644 src/rez/cli/selftest.py create mode 100644 src/rez/cli/status.py create mode 100644 src/rez/cli/suite.py create mode 100644 src/rez/cli/test.py create mode 100644 src/rez/cli/view.py create mode 100644 src/rez/cli/yaml2py.py create mode 100644 src/rez/completion/complete.csh create mode 100644 src/rez/completion/complete.sh create mode 100644 src/rez/config.py create mode 100644 src/rez/developer_package.py create mode 100644 src/rez/exceptions.py create mode 100644 src/rez/package_bind.py create mode 100644 src/rez/package_copy.py create mode 100644 src/rez/package_filter.py create mode 100644 src/rez/package_help.py create mode 100644 src/rez/package_maker__.py create mode 100644 src/rez/package_order.py create mode 100644 src/rez/package_py_utils.py create mode 100644 src/rez/package_repository.py create mode 100644 src/rez/package_resources_.py create mode 100644 src/rez/package_search.py create mode 100644 src/rez/package_serialise.py create mode 100644 src/rez/package_test.py create mode 100644 src/rez/packages_.py create mode 100644 src/rez/pip.py create mode 100644 src/rez/plugin_managers.py create mode 100644 src/rez/release_hook.py create mode 100644 src/rez/release_vcs.py create mode 100644 src/rez/resolved_context.py create mode 100644 src/rez/resolver.py create mode 100644 src/rez/rex.py create mode 100644 src/rez/rex_bindings.py create mode 100644 src/rez/rezconfig.py create mode 100644 src/rez/serialise.py create mode 100644 src/rez/shells.py create mode 100644 src/rez/solver.py create mode 100644 src/rez/status.py create mode 100644 src/rez/suite.py create mode 100644 src/rez/system.py create mode 100644 src/rez/tests/README create mode 100644 src/rez/tests/__init__.py create mode 100644 src/rez/tests/data/builds/packages/anti/1.0.0/package.py create mode 100644 src/rez/tests/data/builds/packages/anti/1.0.0/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/bah/2.1/bah/__init__.py create mode 100644 src/rez/tests/data/builds/packages/bah/2.1/package.py create mode 100644 src/rez/tests/data/builds/packages/bah/2.1/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/build_util/1/build_util/__init__.py create mode 100644 src/rez/tests/data/builds/packages/build_util/1/package.py create mode 100644 src/rez/tests/data/builds/packages/build_util/1/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/floob/floob/__init__.py create mode 100644 src/rez/tests/data/builds/packages/floob/package.py create mode 100644 src/rez/tests/data/builds/packages/floob/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.0.0/foo/__init__.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.0.0/package.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.0.0/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.1.0/foo/__init__.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.1.0/package.py create mode 100644 src/rez/tests/data/builds/packages/foo/1.1.0/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/hello/1.0/Makefile create mode 100644 src/rez/tests/data/builds/packages/hello/1.0/lib/main.cpp create mode 100644 src/rez/tests/data/builds/packages/hello/1.0/package.py create mode 100644 src/rez/tests/data/builds/packages/loco/3/package.py create mode 100644 src/rez/tests/data/builds/packages/loco/3/rezbuild.py create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/CMakeLists.txt create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/lib/CMakeLists.txt create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/lib/ghetto_greet.cpp create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/lib/ghetto_greet.h create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/package.py create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/util/CMakeLists.txt create mode 100644 src/rez/tests/data/builds/packages/sup_world/3.8/util/main.cpp create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/CMakeLists.txt create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/package.py create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/ghettoTranslator.cpp create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/ghettoTranslator.h create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/lolTranslator.cpp create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/lolTranslator.h create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/translator.cpp create mode 100644 src/rez/tests/data/builds/packages/translate_lib/2.2.0/src/translator.h create mode 100644 src/rez/tests/data/builds/packages/whack/package.py create mode 100644 src/rez/tests/data/builds/packages/whack/rezbuild.py create mode 100644 src/rez/tests/data/commands/packages/rextest/1.1/package.yaml create mode 100644 src/rez/tests/data/commands/packages/rextest/1.2/package.yaml create mode 100644 src/rez/tests/data/commands/packages/rextest/1.3/package.py create mode 100644 src/rez/tests/data/commands/packages/rextest2/2/package.py create mode 100644 src/rez/tests/data/config/package.py create mode 100644 src/rez/tests/data/config/test1.yaml create mode 100644 src/rez/tests/data/config/test2.py create mode 100644 src/rez/tests/data/packages/developer/package.yaml create mode 100644 src/rez/tests/data/packages/developer_changed/package.yaml create mode 100644 src/rez/tests/data/packages/developer_dynamic/package.py create mode 100644 src/rez/tests/data/packages/developer_novar/package.yaml create mode 100644 src/rez/tests/data/packages/developer_novar_changed/package.yaml create mode 100644 src/rez/tests/data/packages/py_packages/late_binding/1.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/multi.py create mode 100644 src/rez/tests/data/packages/py_packages/single_unversioned.py create mode 100644 src/rez/tests/data/packages/py_packages/single_versioned.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/1.0.5/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/1.0.6/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/1.1.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/1.1.1/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/1.2.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/2.0.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/2.1.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/timestamped/2.1.5/package.py create mode 100644 src/rez/tests/data/packages/py_packages/unversioned_py/package.py create mode 100644 src/rez/tests/data/packages/py_packages/variants_py/2.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/versioned/2.0/package.py create mode 100644 src/rez/tests/data/packages/py_packages/versioned/3.0/package.py create mode 100644 src/rez/tests/data/packages/yaml_packages/multi.yaml create mode 100644 src/rez/tests/data/packages/yaml_packages/single_unversioned.yaml create mode 100644 src/rez/tests/data/packages/yaml_packages/single_versioned.yaml create mode 100644 src/rez/tests/data/packages/yaml_packages/unversioned/package.yaml create mode 100644 src/rez/tests/data/packages/yaml_packages/versioned/1.0/package.yaml create mode 100644 src/rez/tests/data/packages/yaml_packages/versioned/2.0/package.yaml create mode 100644 src/rez/tests/data/python/early_bind/__init__.py create mode 100644 src/rez/tests/data/python/early_bind/early_utils.py create mode 100644 src/rez/tests/data/python/late_bind/late_utils.py create mode 100644 src/rez/tests/data/release/data/data.txt create mode 100644 src/rez/tests/data/release/package.yaml create mode 100644 src/rez/tests/data/release/rezbuild.py create mode 100644 src/rez/tests/data/release/variants/package.yaml create mode 100644 src/rez/tests/data/release/variants/rezbuild.py create mode 100644 src/rez/tests/data/release/variants/spangle/1.0/package.yaml create mode 100644 src/rez/tests/data/release/variants/spangle/1.1/package.yaml create mode 100644 src/rez/tests/data/release/variants/spangle/2.0/package.yaml create mode 100644 src/rez/tests/data/solver/packages/README create mode 100644 src/rez/tests/data/solver/packages/bahish/1/package.py create mode 100644 src/rez/tests/data/solver/packages/bahish/2/package.py create mode 100644 src/rez/tests/data/solver/packages/nada/package.py create mode 100644 src/rez/tests/data/solver/packages/nopy/2.1/package.py create mode 100644 src/rez/tests/data/solver/packages/pybah/4/package.py create mode 100644 src/rez/tests/data/solver/packages/pybah/5/package.py create mode 100644 src/rez/tests/data/solver/packages/pydad/1/package.py create mode 100644 src/rez/tests/data/solver/packages/pydad/2/package.py create mode 100644 src/rez/tests/data/solver/packages/pydad/3/package.py create mode 100644 src/rez/tests/data/solver/packages/pyfoo/3.0.0/package.py create mode 100644 src/rez/tests/data/solver/packages/pyfoo/3.1.0/package.py create mode 100644 src/rez/tests/data/solver/packages/pymum/1/package.py create mode 100644 src/rez/tests/data/solver/packages/pymum/2/package.py create mode 100644 src/rez/tests/data/solver/packages/pymum/3/package.py create mode 100644 src/rez/tests/data/solver/packages/pyodd/1/package.py create mode 100644 src/rez/tests/data/solver/packages/pyodd/2/package.py create mode 100644 src/rez/tests/data/solver/packages/pyson/1/package.py create mode 100644 src/rez/tests/data/solver/packages/pyson/2/package.py create mode 100644 src/rez/tests/data/solver/packages/pysplit/5/package.py create mode 100644 src/rez/tests/data/solver/packages/pysplit/6/package.py create mode 100644 src/rez/tests/data/solver/packages/pysplit/7/package.py create mode 100644 src/rez/tests/data/solver/packages/python/2.5.2/package.py create mode 100644 src/rez/tests/data/solver/packages/python/2.6.0/package.py create mode 100644 src/rez/tests/data/solver/packages/python/2.6.8/package.py create mode 100644 src/rez/tests/data/solver/packages/python/2.7.0/package.py create mode 100644 src/rez/tests/data/solver/packages/pyvariants/2/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_end/1.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_end/2.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_end/3.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_end/4.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_mid1/1.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_mid1/2.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_mid2/1.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_mid2/2.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_start/1.0/package.py create mode 100644 src/rez/tests/data/solver/packages/test_variant_split_start/2.0/package.py create mode 100644 src/rez/tests/data/suites/packages/bah/package.yaml create mode 100644 src/rez/tests/data/suites/packages/eek/package.yaml create mode 100644 src/rez/tests/data/suites/packages/foo/package.yaml create mode 100644 src/rez/tests/test_build.py create mode 100644 src/rez/tests/test_commands.py create mode 100644 src/rez/tests/test_completion.py create mode 100644 src/rez/tests/test_config.py create mode 100644 src/rez/tests/test_context.py create mode 100644 src/rez/tests/test_copy_package.py create mode 100644 src/rez/tests/test_formatter.py create mode 100644 src/rez/tests/test_imports.py create mode 100644 src/rez/tests/test_logging.py create mode 100644 src/rez/tests/test_packages.py create mode 100644 src/rez/tests/test_release.py create mode 100644 src/rez/tests/test_resources_.py create mode 100644 src/rez/tests/test_rex.py create mode 100644 src/rez/tests/test_schema.py create mode 100644 src/rez/tests/test_shells.py create mode 100644 src/rez/tests/test_solver.py create mode 100644 src/rez/tests/test_suites.py create mode 100644 src/rez/tests/test_version.py create mode 100644 src/rez/tests/util.py create mode 100644 src/rez/util.py create mode 100644 src/rez/utils/__init__.py create mode 100644 src/rez/utils/_version.py create mode 100644 src/rez/utils/amqp.py create mode 100644 src/rez/utils/backcompat.py create mode 100644 src/rez/utils/colorize.py create mode 100644 src/rez/utils/data_utils.py create mode 100644 src/rez/utils/diff_packages.py create mode 100644 src/rez/utils/filesystem.py create mode 100644 src/rez/utils/formatting.py create mode 100644 src/rez/utils/graph_utils.py create mode 100644 src/rez/utils/json.py create mode 100644 src/rez/utils/lint_helper.py create mode 100644 src/rez/utils/logging.conf create mode 100644 src/rez/utils/logging_.py create mode 100644 src/rez/utils/memcached.py create mode 100644 src/rez/utils/patching.py create mode 100644 src/rez/utils/platform_.py create mode 100644 src/rez/utils/platform_mapped.py create mode 100644 src/rez/utils/py_dist.py create mode 100644 src/rez/utils/resources.py create mode 100644 src/rez/utils/schema.py create mode 100644 src/rez/utils/scope.py create mode 100644 src/rez/utils/sourcecode.py create mode 100644 src/rez/utils/system.py create mode 100644 src/rez/utils/yaml.py create mode 100644 src/rez/vendor/__init__.py create mode 100644 src/rez/vendor/amqp/LICENSE create mode 100644 src/rez/vendor/amqp/__init__.py create mode 100644 src/rez/vendor/amqp/abstract_channel.py create mode 100644 src/rez/vendor/amqp/basic_message.py create mode 100644 src/rez/vendor/amqp/channel.py create mode 100644 src/rez/vendor/amqp/connection.py create mode 100644 src/rez/vendor/amqp/exceptions.py create mode 100644 src/rez/vendor/amqp/five.py create mode 100644 src/rez/vendor/amqp/method_framing.py create mode 100644 src/rez/vendor/amqp/protocol.py create mode 100644 src/rez/vendor/amqp/serialization.py create mode 100644 src/rez/vendor/amqp/transport.py create mode 100644 src/rez/vendor/amqp/utils.py create mode 100644 src/rez/vendor/argcomplete/LICENSE.rst create mode 100644 src/rez/vendor/argcomplete/__init__.py create mode 100644 src/rez/vendor/argcomplete/completers.py create mode 100644 src/rez/vendor/argcomplete/my_argparse.py create mode 100644 src/rez/vendor/argcomplete/my_shlex.py create mode 100644 src/rez/vendor/argparse.py create mode 100644 src/rez/vendor/atomicwrites/LICENSE create mode 100644 src/rez/vendor/atomicwrites/__init__.py create mode 100755 src/rez/vendor/colorama/__init__.py create mode 100755 src/rez/vendor/colorama/ansi.py create mode 100755 src/rez/vendor/colorama/ansitowin32.py create mode 100755 src/rez/vendor/colorama/initialise.py create mode 100755 src/rez/vendor/colorama/win32.py create mode 100755 src/rez/vendor/colorama/winterm.py create mode 100644 src/rez/vendor/distlib/__init__.py create mode 100644 src/rez/vendor/distlib/_backport/__init__.py create mode 100644 src/rez/vendor/distlib/_backport/misc.py create mode 100644 src/rez/vendor/distlib/_backport/shutil.py create mode 100644 src/rez/vendor/distlib/_backport/sysconfig.cfg create mode 100644 src/rez/vendor/distlib/_backport/sysconfig.py create mode 100644 src/rez/vendor/distlib/_backport/tarfile.py create mode 100644 src/rez/vendor/distlib/compat.py create mode 100644 src/rez/vendor/distlib/database.py create mode 100644 src/rez/vendor/distlib/index.py create mode 100644 src/rez/vendor/distlib/locators.py create mode 100644 src/rez/vendor/distlib/manifest.py create mode 100644 src/rez/vendor/distlib/markers.py create mode 100644 src/rez/vendor/distlib/metadata.py create mode 100644 src/rez/vendor/distlib/resources.py create mode 100644 src/rez/vendor/distlib/scripts.py create mode 100644 src/rez/vendor/distlib/t32.exe create mode 100644 src/rez/vendor/distlib/t64.exe create mode 100644 src/rez/vendor/distlib/util.py create mode 100644 src/rez/vendor/distlib/version.py create mode 100644 src/rez/vendor/distlib/w32.exe create mode 100644 src/rez/vendor/distlib/w64.exe create mode 100644 src/rez/vendor/distlib/wheel.py create mode 100644 src/rez/vendor/enum/LICENSE create mode 100644 src/rez/vendor/enum/__init__.py create mode 100644 src/rez/vendor/enum/doc/enum.rst create mode 100644 src/rez/vendor/enum/enum.py create mode 100644 src/rez/vendor/lockfile/LICENSE create mode 100644 src/rez/vendor/lockfile/RELEASE-NOTES create mode 100644 src/rez/vendor/lockfile/__init__.py create mode 100644 src/rez/vendor/lockfile/linklockfile.py create mode 100644 src/rez/vendor/lockfile/mkdirlockfile.py create mode 100644 src/rez/vendor/lockfile/pidlockfile.py create mode 100644 src/rez/vendor/lockfile/sqlitelockfile.py create mode 100644 src/rez/vendor/lockfile/symlinklockfile.py create mode 100644 src/rez/vendor/memcache/__init__.py create mode 100644 src/rez/vendor/memcache/memcache.py create mode 100644 src/rez/vendor/progress/LICENSE create mode 100644 src/rez/vendor/progress/__init__.py create mode 100644 src/rez/vendor/progress/bar.py create mode 100644 src/rez/vendor/progress/counter.py create mode 100644 src/rez/vendor/progress/helpers.py create mode 100644 src/rez/vendor/progress/spinner.py create mode 100644 src/rez/vendor/pydot/LICENSE create mode 100644 src/rez/vendor/pydot/__init__.py create mode 100644 src/rez/vendor/pydot/dot_parser.py create mode 100644 src/rez/vendor/pydot/pydot.py create mode 100644 src/rez/vendor/pygraph/COPYING create mode 100644 src/rez/vendor/pygraph/__init__.py create mode 100644 src/rez/vendor/pygraph/algorithms/__init__.py create mode 100644 src/rez/vendor/pygraph/algorithms/accessibility.py create mode 100644 src/rez/vendor/pygraph/algorithms/critical.py create mode 100644 src/rez/vendor/pygraph/algorithms/cycles.py create mode 100644 src/rez/vendor/pygraph/algorithms/filters/__init__.py create mode 100644 src/rez/vendor/pygraph/algorithms/filters/find.py create mode 100644 src/rez/vendor/pygraph/algorithms/filters/null.py create mode 100644 src/rez/vendor/pygraph/algorithms/filters/radius.py create mode 100644 src/rez/vendor/pygraph/algorithms/generators.py create mode 100644 src/rez/vendor/pygraph/algorithms/heuristics/__init__.py create mode 100644 src/rez/vendor/pygraph/algorithms/heuristics/chow.py create mode 100644 src/rez/vendor/pygraph/algorithms/heuristics/euclidean.py create mode 100644 src/rez/vendor/pygraph/algorithms/minmax.py create mode 100644 src/rez/vendor/pygraph/algorithms/pagerank.py create mode 100644 src/rez/vendor/pygraph/algorithms/searching.py create mode 100644 src/rez/vendor/pygraph/algorithms/sorting.py create mode 100644 src/rez/vendor/pygraph/algorithms/traversal.py create mode 100644 src/rez/vendor/pygraph/algorithms/utils.py create mode 100644 src/rez/vendor/pygraph/classes/__init__.py create mode 100644 src/rez/vendor/pygraph/classes/digraph.py create mode 100644 src/rez/vendor/pygraph/classes/exceptions.py create mode 100644 src/rez/vendor/pygraph/classes/graph.py create mode 100644 src/rez/vendor/pygraph/classes/hypergraph.py create mode 100644 src/rez/vendor/pygraph/mixins/__init__.py create mode 100644 src/rez/vendor/pygraph/mixins/basegraph.py create mode 100644 src/rez/vendor/pygraph/mixins/common.py create mode 100644 src/rez/vendor/pygraph/mixins/labeling.py create mode 100644 src/rez/vendor/pygraph/readwrite/__init__.py create mode 100644 src/rez/vendor/pygraph/readwrite/dot.py create mode 100644 src/rez/vendor/pygraph/readwrite/markup.py create mode 100644 src/rez/vendor/pyparsing/LICENSE create mode 100644 src/rez/vendor/pyparsing/__init__.py create mode 100644 src/rez/vendor/pyparsing/pyparsing.py create mode 100644 src/rez/vendor/schema/LICENSE-MIT create mode 100644 src/rez/vendor/schema/__init__.py create mode 100644 src/rez/vendor/schema/schema.py create mode 100644 src/rez/vendor/schema/test_schema.py create mode 100644 src/rez/vendor/six/CHANGES create mode 100644 src/rez/vendor/six/LICENSE create mode 100644 src/rez/vendor/six/README create mode 100644 src/rez/vendor/six/__init__.py create mode 100644 src/rez/vendor/six/six.py create mode 100644 src/rez/vendor/sortedcontainers/LICENSE create mode 100644 src/rez/vendor/sortedcontainers/__init__.py create mode 100644 src/rez/vendor/sortedcontainers/sorteddict.py create mode 100644 src/rez/vendor/sortedcontainers/sortedlist.py create mode 100644 src/rez/vendor/sortedcontainers/sortedset.py create mode 100644 src/rez/vendor/unittest2/__init__.py create mode 100644 src/rez/vendor/unittest2/__main__.py create mode 100644 src/rez/vendor/unittest2/case.py create mode 100644 src/rez/vendor/unittest2/collector.py create mode 100644 src/rez/vendor/unittest2/compatibility.py create mode 100644 src/rez/vendor/unittest2/loader.py create mode 100644 src/rez/vendor/unittest2/main.py create mode 100644 src/rez/vendor/unittest2/result.py create mode 100644 src/rez/vendor/unittest2/runner.py create mode 100644 src/rez/vendor/unittest2/signals.py create mode 100644 src/rez/vendor/unittest2/suite.py create mode 100644 src/rez/vendor/unittest2/util.py create mode 100644 src/rez/vendor/version/__init__.py create mode 100644 src/rez/vendor/version/requirement.py create mode 100644 src/rez/vendor/version/test.py create mode 100644 src/rez/vendor/version/util.py create mode 100644 src/rez/vendor/version/version.py create mode 100644 src/rez/vendor/yaml/LICENSE create mode 100644 src/rez/vendor/yaml/__init__.py create mode 100644 src/rez/vendor/yaml/composer.py create mode 100644 src/rez/vendor/yaml/constructor.py create mode 100644 src/rez/vendor/yaml/cyaml.py create mode 100644 src/rez/vendor/yaml/dumper.py create mode 100644 src/rez/vendor/yaml/emitter.py create mode 100644 src/rez/vendor/yaml/error.py create mode 100644 src/rez/vendor/yaml/events.py create mode 100644 src/rez/vendor/yaml/loader.py create mode 100644 src/rez/vendor/yaml/nodes.py create mode 100644 src/rez/vendor/yaml/parser.py create mode 100644 src/rez/vendor/yaml/reader.py create mode 100644 src/rez/vendor/yaml/representer.py create mode 100644 src/rez/vendor/yaml/resolver.py create mode 100644 src/rez/vendor/yaml/scanner.py create mode 100644 src/rez/vendor/yaml/serializer.py create mode 100644 src/rez/vendor/yaml/tokens.py create mode 100644 src/rez/wrapper.py create mode 100644 src/rezgui/__init__.py create mode 100644 src/rezgui/app.py create mode 100644 src/rezgui/dialogs/AboutDialog.py create mode 100644 src/rezgui/dialogs/BrowsePackageDialog.py create mode 100644 src/rezgui/dialogs/ImageViewerDialog.py create mode 100644 src/rezgui/dialogs/ProcessDialog.py create mode 100644 src/rezgui/dialogs/ResolveDialog.py create mode 100644 src/rezgui/dialogs/VariantVersionsDialog.py create mode 100644 src/rezgui/dialogs/WriteGraphDialog.py create mode 100644 src/rezgui/dialogs/__init__.py create mode 100644 src/rezgui/icons/advanced_resolve.png create mode 100644 src/rezgui/icons/changelog.png create mode 100644 src/rezgui/icons/clock.png create mode 100644 src/rezgui/icons/clock_warning.png create mode 100644 src/rezgui/icons/cog.png create mode 100644 src/rezgui/icons/context.png create mode 100644 src/rezgui/icons/context_settings.png create mode 100644 src/rezgui/icons/depends.png create mode 100644 src/rezgui/icons/diff.png create mode 100644 src/rezgui/icons/diff_to_disk.png create mode 100644 src/rezgui/icons/diff_to_other.png create mode 100644 src/rezgui/icons/equal_to.png create mode 100644 src/rezgui/icons/equalish.png create mode 100644 src/rezgui/icons/error.png create mode 100644 src/rezgui/icons/excluded.png create mode 100644 src/rezgui/icons/find.png create mode 100644 src/rezgui/icons/github_32.png create mode 100644 src/rezgui/icons/graph.png create mode 100644 src/rezgui/icons/greater_than.png create mode 100644 src/rezgui/icons/greater_than_1.png create mode 100644 src/rezgui/icons/greater_than_2.png create mode 100644 src/rezgui/icons/greater_than_3.png create mode 100644 src/rezgui/icons/green_tick.png create mode 100644 src/rezgui/icons/green_white_tick.png create mode 100644 src/rezgui/icons/help.png create mode 100644 src/rezgui/icons/info.png create mode 100644 src/rezgui/icons/less_than.png create mode 100644 src/rezgui/icons/less_than_1.png create mode 100644 src/rezgui/icons/less_than_2.png create mode 100644 src/rezgui/icons/less_than_3.png create mode 100644 src/rezgui/icons/local.png create mode 100644 src/rezgui/icons/lock.png create mode 100644 src/rezgui/icons/lock_2.png create mode 100644 src/rezgui/icons/lock_2_faint.png create mode 100644 src/rezgui/icons/lock_3.png create mode 100644 src/rezgui/icons/lock_3_faint.png create mode 100644 src/rezgui/icons/lock_4.png create mode 100644 src/rezgui/icons/lock_4_faint.png create mode 100644 src/rezgui/icons/lock_faint.png create mode 100644 src/rezgui/icons/missing.png create mode 100644 src/rezgui/icons/new.png create mode 100644 src/rezgui/icons/no_lock.png create mode 100644 src/rezgui/icons/no_lock_faint.png create mode 100644 src/rezgui/icons/old_man.png create mode 100644 src/rezgui/icons/package.png create mode 100644 src/rezgui/icons/pink.png create mode 100644 src/rezgui/icons/resolve.png create mode 100644 src/rezgui/icons/revert.png create mode 100644 src/rezgui/icons/revert_to_diff.png create mode 100644 src/rezgui/icons/revert_to_disk.png create mode 100644 src/rezgui/icons/spanner.png create mode 100644 src/rezgui/icons/terminal.png create mode 100644 src/rezgui/icons/time_lock.png create mode 100644 src/rezgui/icons/tools.png create mode 100644 src/rezgui/icons/variant.png create mode 100644 src/rezgui/icons/versions.png create mode 100644 src/rezgui/icons/warning.png create mode 100644 src/rezgui/icons/yellow_tick.png create mode 100644 src/rezgui/icons/yellow_white_tick.png create mode 100644 src/rezgui/mixins/ContextViewMixin.py create mode 100644 src/rezgui/mixins/StoreSizeMixin.py create mode 100644 src/rezgui/mixins/__init__.py create mode 100644 src/rezgui/models/ContextModel.py create mode 100644 src/rezgui/models/__init__.py create mode 100644 src/rezgui/objects/App.py create mode 100644 src/rezgui/objects/Config.py create mode 100644 src/rezgui/objects/LoadPackagesThread.py create mode 100644 src/rezgui/objects/ProcessTrackerThread.py create mode 100644 src/rezgui/objects/ResolveThread.py create mode 100644 src/rezgui/objects/__init__.py create mode 100644 src/rezgui/qt.py create mode 100644 src/rezgui/rezguiconfig create mode 100644 src/rezgui/util.py create mode 100644 src/rezgui/widgets/BrowsePackagePane.py create mode 100644 src/rezgui/widgets/BrowsePackageWidget.py create mode 100644 src/rezgui/widgets/ChangelogEdit.py create mode 100644 src/rezgui/widgets/ConfiguredSplitter.py create mode 100644 src/rezgui/widgets/ContextDetailsWidget.py create mode 100644 src/rezgui/widgets/ContextEnvironTable.py create mode 100644 src/rezgui/widgets/ContextEnvironWidget.py create mode 100644 src/rezgui/widgets/ContextManagerWidget.py create mode 100644 src/rezgui/widgets/ContextResolveTimeLabel.py create mode 100644 src/rezgui/widgets/ContextSettingsWidget.py create mode 100644 src/rezgui/widgets/ContextTableWidget.py create mode 100644 src/rezgui/widgets/ContextToolsWidget.py create mode 100644 src/rezgui/widgets/EffectivePackageCellWidget.py create mode 100644 src/rezgui/widgets/FindPopup.py create mode 100644 src/rezgui/widgets/IconButton.py create mode 100644 src/rezgui/widgets/ImageViewerWidget.py create mode 100644 src/rezgui/widgets/PackageLineEdit.py create mode 100644 src/rezgui/widgets/PackageLoadingWidget.py create mode 100644 src/rezgui/widgets/PackageSelectWidget.py create mode 100644 src/rezgui/widgets/PackageTabWidget.py create mode 100644 src/rezgui/widgets/PackageVersionsTable.py create mode 100644 src/rezgui/widgets/SearchableTextEdit.py create mode 100644 src/rezgui/widgets/StreamableTextEdit.py create mode 100644 src/rezgui/widgets/TimeSelecterPopup.py create mode 100644 src/rezgui/widgets/TimestampWidget.py create mode 100644 src/rezgui/widgets/ToolWidget.py create mode 100644 src/rezgui/widgets/VariantCellWidget.py create mode 100644 src/rezgui/widgets/VariantDetailsWidget.py create mode 100644 src/rezgui/widgets/VariantHelpWidget.py create mode 100644 src/rezgui/widgets/VariantSummaryWidget.py create mode 100644 src/rezgui/widgets/VariantToolsList.py create mode 100644 src/rezgui/widgets/VariantVersionsTable.py create mode 100644 src/rezgui/widgets/VariantVersionsWidget.py create mode 100644 src/rezgui/widgets/VariantsList.py create mode 100644 src/rezgui/widgets/ViewGraphButton.py create mode 100644 src/rezgui/widgets/__init__.py create mode 100644 src/rezgui/windows/BrowsePackageSubWindow.py create mode 100644 src/rezgui/windows/ContextSubWindow.py create mode 100644 src/rezgui/windows/MainWindow.py create mode 100644 src/rezgui/windows/__init__.py create mode 100644 src/rezplugins/__init__.py create mode 100644 src/rezplugins/build_process/__init__.py create mode 100644 src/rezplugins/build_process/local.py create mode 100644 src/rezplugins/build_process/remote.py create mode 100644 src/rezplugins/build_process/rezconfig create mode 100644 src/rezplugins/build_system/__init__.py create mode 100644 src/rezplugins/build_system/bez.py create mode 100644 src/rezplugins/build_system/cmake.py create mode 100644 src/rezplugins/build_system/cmake_files/Colorize.cmake create mode 100644 src/rezplugins/build_system/cmake_files/FindStaticLibs.cmake create mode 100644 src/rezplugins/build_system/cmake_files/InstallDirs.cmake create mode 100644 src/rezplugins/build_system/cmake_files/InstallFiles.cmake create mode 100644 src/rezplugins/build_system/cmake_files/InstallPython.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezBuild.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezFindPackages.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezInstallCMake.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezInstallContext.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezInstallDoxygen.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezInstallPython.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezPipInstall.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezProject.cmake create mode 100644 src/rezplugins/build_system/cmake_files/RezRepository.cmake create mode 100644 src/rezplugins/build_system/cmake_files/Utils.cmake create mode 100644 src/rezplugins/build_system/custom.py create mode 100644 src/rezplugins/build_system/make.py create mode 100644 src/rezplugins/build_system/rezconfig create mode 100644 src/rezplugins/build_system/rezconfig.py create mode 100644 src/rezplugins/build_system/template_files/Doxyfile create mode 100644 src/rezplugins/package_repository/__init__.py create mode 100644 src/rezplugins/package_repository/filesystem.py create mode 100644 src/rezplugins/package_repository/memory.py create mode 100644 src/rezplugins/package_repository/rezconfig create mode 100644 src/rezplugins/release_hook/__init__.py create mode 100644 src/rezplugins/release_hook/amqp.py create mode 100644 src/rezplugins/release_hook/command.py create mode 100644 src/rezplugins/release_hook/emailer-recipients-example.yaml create mode 100644 src/rezplugins/release_hook/emailer.py create mode 100644 src/rezplugins/release_hook/rezconfig create mode 100644 src/rezplugins/release_vcs/__init__.py create mode 100644 src/rezplugins/release_vcs/git.py create mode 100644 src/rezplugins/release_vcs/hg.py create mode 100644 src/rezplugins/release_vcs/rezconfig create mode 100644 src/rezplugins/release_vcs/stub.py create mode 100644 src/rezplugins/release_vcs/svn.py create mode 100644 src/rezplugins/shell/__init__.py create mode 100644 src/rezplugins/shell/bash.py create mode 100644 src/rezplugins/shell/cmd.py create mode 100644 src/rezplugins/shell/csh.py create mode 100644 src/rezplugins/shell/rezconfig create mode 100644 src/rezplugins/shell/sh.py create mode 100644 src/rezplugins/shell/tcsh.py create mode 100644 src/support/README.md create mode 100644 src/support/package_utils/README create mode 100644 src/support/package_utils/get_committers.sh create mode 100644 src/support/package_utils/set_authors.py create mode 100755 src/support/shotgun_toolkit/rez_app_launch.py create mode 100755 tag.sh create mode 100644 tests/__init__.py create mode 100644 tests/test.py create mode 100644 tox.ini create mode 100644 wiki/README.md create mode 100644 wiki/media/icons/info.png create mode 100644 wiki/media/icons/under_construction.png create mode 100644 wiki/media/icons/warning.png create mode 100644 wiki/media/other_pkg_mgr.png create mode 100644 wiki/media/pkg_path_anatomy.png create mode 100644 wiki/media/rez_banner_128.png create mode 100644 wiki/media/rez_deps_simple_eg.png create mode 100644 wiki/media/rez_env.png create mode 100644 wiki/media/rez_pkg_mgr.png create mode 100644 wiki/pages/Basic-Concepts.md create mode 100644 wiki/pages/Building-Packages.md create mode 100644 wiki/pages/Command-Line-Tools.md create mode 100644 wiki/pages/Contexts.md create mode 100644 wiki/pages/Environment-Variables.md create mode 100644 wiki/pages/FAQ.md create mode 100644 wiki/pages/Getting-Started.md create mode 100644 wiki/pages/Glossary.md create mode 100644 wiki/pages/Package-Commands.md create mode 100644 wiki/pages/Package-Definition-Guide.md create mode 100644 wiki/pages/Suites.md create mode 100644 wiki/pages/Variants.md create mode 100644 wiki/pages/_Configuring-Rez.md create mode 100644 wiki/pages/_Credits.md create mode 100644 wiki/update-wiki.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1cf5b04365 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.pyc +.project +.pydevproject +.nfs* +*.swp +src/rez.egg-info/ +build/ +dist/ +*~ +docs/_build +.DS_Store +.rez-gen-wiki-tmp +LATEST_CHANGELOG.md diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..285491ff2d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: python + +matrix: + include: + - python: 2.7 + os: linux + env: _REZ_SHELL=bash + +install: + - 'if [ "$_REZ_SHELL" == "tcsh" ]; then sudo apt-get install tcsh; fi' + - 'python ./install.py ../rez_install' + +script: + - '../rez_install/bin/rez/rez-selftest -s $_REZ_SHELL' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..28a044ea8a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,266 @@ +# Change Log + +## [2.28.0](https://github.com/nerdvegas/rez/tree/2.28.0) (2019-03-15) +[Full Changelog](https://github.com/nerdvegas/rez/compare/2.27.1...2.28.0) + +**Fixed bugs:** + +- nargs errors for logging_.print_* functions [\#580](https://github.com/nerdvegas/rez/issues/580) + +**Merged pull requests:** + +- Ignore versions if .ignore file exists [\#453](https://github.com/nerdvegas/rez/pull/453) ([Pixomondo](https://github.com/Pixomondo)) +- Fix/logging print nargs [\#581](https://github.com/nerdvegas/rez/pull/581) ([wwfxuk](https://github.com/wwfxuk)) +- package_test.py: fix rez-test header command with % [\#572](https://github.com/nerdvegas/rez/pull/572) ([rodeofx](https://github.com/rodeofx)) +- Call the flush method every time a Printer instance is called [\#540](https://github.com/nerdvegas/rez/pull/540) ([rodeofx](https://github.com/rodeofx)) + +## [2.27.1](https://github.com/nerdvegas/rez/tree/2.27.1) (2019-03-15) +[Full Changelog](https://github.com/nerdvegas/rez/compare/2.27.0...2.27.1) + +**Merged pull requests:** + +- Delete old repository directory [\#576](https://github.com/nerdvegas/rez/pull/576) ([bpabel](https://github.com/bpabel)) + +## [2.27.0](https://github.com/nerdvegas/rez/tree/2.27.0) (2019-01-24) +[Full Changelog](https://github.com/nerdvegas/rez/compare/2.26.4...2.27.0) + +**Implemented enhancements:** + +- facilitate variant install when target package is read-only [\#565](https://github.com/nerdvegas/rez/issues/565) + +**Fixed bugs:** + +- timestamp override no working in package copy [\#568](https://github.com/nerdvegas/rez/issues/568) +- shallow rez-cp can corrupt package if there are overlapping variants [\#563](https://github.com/nerdvegas/rez/issues/563) + +**Merged pull requests:** + +- Issue 568 [\#569](https://github.com/nerdvegas/rez/pull/569) ([nerdvegas](https://github.com/nerdvegas)) +- Issue 565 [\#567](https://github.com/nerdvegas/rez/pull/567) ([nerdvegas](https://github.com/nerdvegas)) +- Issue 563 [\#566](https://github.com/nerdvegas/rez/pull/566) ([nerdvegas](https://github.com/nerdvegas)) + +## 2.26.4 [[#562](https://github.com/nerdvegas/rez/pull/562)] Fixed Regression in 2.24.0 + +#### Addressed Issues + +* [#561](https://github.com/nerdvegas/rez/issues/561) timestamp not written to installed package + +## 2.26.3 [[#560](https://github.com/nerdvegas/rez/pull/560)] Package.py permissions issue + +#### Addressed Issues + +* [#559](https://github.com/nerdvegas/rez/issues/559) package.py permissions issue + +#### Notes + +Fixes issue where installed `package.py` can be set to r/w for only the current user. + +## 2.26.2 [[#557](https://github.com/nerdvegas/rez/pull/557)] Package Copy Fixes For Non-Varianted Packages + +#### Addressed Issues + +* [#556](https://github.com/nerdvegas/rez/issues/556) rez-cp briefly copies original package definition in non-varianted packages +* [#555](https://github.com/nerdvegas/rez/issues/555) rez-cp inconsistent symlinking when --shallow=true +* [#554](https://github.com/nerdvegas/rez/issues/554) rez-cp doesn't keep file metadata in some cases + +#### Notes + +There were various minor issues related to copying non-varianted packages. + +## 2.26.1 [[#552](https://github.com/nerdvegas/rez/pull/552)] Bugfix in Package Copy + +#### Addressed Issues + +* [#551](https://github.com/nerdvegas/rez/issues/551) package copy fails if symlinks in root dir + +#### Notes + +This was failing when symlinks were present within a non-varianted package being copied. Now, these +symlinks are retained in the target package, unless `--follow-symlinks` is specified. + +## 2.26.0 [[#550](https://github.com/nerdvegas/rez/pull/550)] Build System Detection Fixes + +#### Addressed Issues + +* [#549](https://github.com/nerdvegas/rez/issues/549) '--build-system' rez-build option not always + available + +#### Notes + +To fix this issue: +* The '--build-system' rez-build option is now always present. +* To provide further control over the build system type, the package itself can now specify its build + system - see https://github.com/nerdvegas/rez/wiki/Package-Definition-Guide#build_system + +#### COMPATIBILITY ISSUE! + +Unfortunately, the 'cmake' build system had its own '--build-system' commandline option also. This +was possible because previous rez versions suppressed the standard '--build-system' option if only +one valid build system was present for a given package working directory. **This option has been +changed to '--cmake-build-system'**. + + +## 2.25.0 [[#548](https://github.com/nerdvegas/rez/pull/548)] Various Build-related issues + +#### Addressed Issues + +* [#433](https://github.com/nerdvegas/rez/issues/433): "package_definition_build_python_paths" defined + paths are not available from top level in package.py +* [#442](https://github.com/nerdvegas/rez/issues/442): "rez-depends" and "private_build_requires" +* [#416](https://github.com/nerdvegas/rez/issues/416): Need currently-building-variant build variables +* [#547](https://github.com/nerdvegas/rez/issues/547): rez-cp follows symlinks within package payload + +#### Notes + +The biggest update in this release is the introduction of new variables accessible at early-bind time: +building, build_variant_index and build_variant_requires. This allows you to do things like define +different private_build_requires per-variant, or a requires that is different at runtime than it is +at build time. In order to get this to work, a package.py is now re-evaluated multiple times when a +build occurs - once pre-build (where 'building' is set to False), and once per variant build. Please +see the updated wiki for more details: https://github.com/nerdvegas/rez/wiki/Package-Definition-Guide#available-objects + +A new build-time env-var, REZ_BUILD_VARIANT_REQUIRES, has been added. This mirrors the new +build_variant_requires var mentioned above. + +rez-depends has been updated to only include the private_build_requires of the package being queried +(previously, all packages' private build reqs were included, which is not useful). Recall that the +previous release fixes the issue where private_build_requires was being stripped from released +packages. + +The entirety of a package definition file can now see the extra build-time modules available via the +package_definition_build_python_paths config setting. Previously, only early bound functions could +see these. + +There was an issue with package copying (and thus the rez-cp tool) where symlinks within a package's +payload were expanded out to their source files at copy time. The default now is to keep such symlinks +intact - but hte previous behavior can still be accessed with the rez-cp --follow-symlinks option. + + +## 2.24.0: Package Copying + +This release adds a new tool, rez-cp, for copying packages/variants from one package repository to +another, with optional renaming/reversioning. The associated API can be found in src/package_copy.py. + +#### Addressed Issues + +* #541 +* #510 +* #477 + +#### Notes + +* Package definition file writes are now atomic; +* private_build_requires is kept in installed/released packages; +* Fixes include modules not being copied into released packages; +* File lock is no longer created when variant installation happens in dry mode. + + +## 2.23.1: Fixed Regression in 2.20.0 + +#### Addressed Issues + +* #532 + +#### Notes + +Bug was introduced in: https://github.com/nerdvegas/rez/releases/tag/2.20.0 + + +## 2.23.0: Package Usage Tracking, Better Config Overrides + +#### Addressed Issues + +* #528 + +#### Notes + +Two new features are added in this release: + +Override any config setting with an env-var. For any setting "foo", you can now set the env-var +REZ_FOO_JSON to a JSON-encoded string. This works for any config setting. Note that the existing +REZ_FOO env-var overrides are still in place also; if both are defined, REZ_FOO takes precedence. +This feature means you can now override some of the more complicated settings with env-vars, such as +package_filter. + +Track context creation and sourcing via AMQP. Messages are published (on a separate thread) to the +nominated broker/exchange/routing_key. You have control over what parts of the context are published. +For more details: https://github.com/nerdvegas/rez/blob/master/src/rez/rezconfig.py#L414 + +The embedded simplejson lib was removed. The native json lib is used instead, and for cases where loads-without-unicoding-everything is needed, utils/json.py now addresses that instead. + + +## 2.22.1: Stdin-related fixes + +#### Addressed Issues + +* #512 +* #526 + + +## 2.22.0: Search API + +PR: #213 + +#### Notes + +Package/variant/family search API is now available in package_search.py. This gives the same +functionality as provided by the rez-search CLI tool. + + +## 2.21.0: Added mingw as a rez build_system for cmake + +PR: #501 + + +## 2.20.1: Windows Fixes + +#### Merged PRs + +* #490: Fix alias command in Windows when PATH is modified +* #489: Fix cmd.exe not escaping special characters +* #482: Fix selftest getting stuck on Windows + +#### Addressed Issues + +* #389 +* #343 +* #432 +* #481 + + +## 2.20.0: Better CLI Arg Parsing + +PR: #523 + +#### Addressed Issues + +* #492 + +#### Notes + +The rez-python command now supports all native python args and passes those through to its python +subprocess - so you can now shebang with rez-python if that is useful. + +More broadly, rez commands now parse CLI args correctly for each case. Many commands previously +accepted rez-env-style commands (eg rez-env pkgA -- somecommand -- i am ignored), but simply ignored +extraneous args after -- tokens. + + +## 2.19.1: Fixed bug with rez-build and package preprocess + +#### Merged PRs + +* #522 + +#### Addressed Issues + +* #514 + +#### Notes + +The problem occurred because the preprocess function was attempting to be serialized when the package +definition is cached to memcache. However, this function is stripped in installed packages; +furthermore, caching "developer packages" (ie unbuilt packages) was never intentional. + +This release disables memcaching of developer packages, thus avoiding the bug and bringing back +originally intended behavior. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..2075920b9e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing To Rez + +If you would like to contribute code you can do so through GitHub by forking the repository and +sending a pull request. Please follow these guidelines: + +* Make every effort to follow existing conventions and style; +* Follow [PEP8](https://www.python.org/dev/peps/pep-0008/); +* Follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) + for docstrings; +* Use *spaces*, not *tabs*; +* Use [this format](https://help.github.com/articles/closing-issues-using-keywords/) to mention the + issue(s) your PR closes; +* Add relevant tests to demonstrate that your changes work; +* Add relevant documentation (see *wiki* source directory) to document your changes, if applicable. + +## Reporting Bugs + +If you report a bug, please ensure to specify the following: + +* Rez version (e.g. 2.18.0); +* Platform and operating system you were using; +* Contextual information (what were you trying to do using Rez); +* Simplest possible steps to reproduce. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000000..341c30bda4 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000000..ced70a010c --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,3 @@ +# Installation + +See https://github.com/nerdvegas/rez/wiki/Getting-Started#installation diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000..985b3eb97d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include src/rez/rezconfig +include src/rezplugins/build_system/template_files/Doxyfile +recursive-include src/rez README* +recursive-include src/rezplugins *.cmake Doxyfile + diff --git a/README.md b/README.md new file mode 100644 index 0000000000..3f2ec3605f --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +[![Build Status](https://travis-ci.org/nerdvegas/rez.svg?branch=master)](https://travis-ci.org/nerdvegas/rez) + +![logo](media/rez_banner_256.png) + +- [What Is Rez?](#what-is-rez) +- [The Basics](#the-basics) +- [Examples](#examples) +- [Quickstart](#quickstart) +- [Building Your First Package](#building-your-first-package) +- [Features](#features) + + +## What Is Rez? + +Rez is a cross-platform package manager with a difference. Using Rez you can create +standalone environments configured for a given set of packages. However, unlike many +other package managers, packages are not installed into these standalone environments. +Instead, all package versions are installed into a central repository, and standalone +environments reference these existing packages. This means that configured environments +are lightweight, and very fast to create, often taking just a few seconds to configure +despite containing hundreds of packages. + +See [the wiki](https://github.com/nerdvegas/rez/wiki) for full documentation. + +

+ + +
Typical package managers install packages into an environment +

+ +
+

+ + +
Rez installs packages once, and configures environments dynamically +

+ +
+Rez takes a list of package requests, and constructs the target environment, resolving +all the necessary package dependencies. Any type of software package is supported - +compiled, python, applications and libraries. + + +## The Basics + +Packages are stored in repositories on disk. Each package has a single concise +definition file (*package.py*) that defines its dependencies, its commands (how it +configures the environment containing it), and other metadata. For example, the +following is the package definition file for the popular *requests* python module: + + name = "requests" + + version = "2.8.1" + + authors = ["Kenneth Reitz"] + + requires = [ + "python-2.7+" + ] + + def commands(): + env.PYTHONPATH.append("{root}/python") + +This package requires python-2.7 or greater. When used, the 'python' subdirectory +within its install location is appended to the PYTHONPATH environment variable. + +When an environment is created with the rez API or *rez-env* tool, a dependency +resolution algorithm tracks package requirements and resolves to a list of needed +packages. The commands from these packages are concatenated and evaluated, resulting +in a configured environment. Rez is able to configure environments containing +hundreds of packages, often within a few seconds. Resolves can also be saved to file, +and when re-evaluated later will reconstruct the same environment once more. + + +## Examples + +This example places the user into a resolved shell containing the requested packages, +using the [rez-env](https://github.com/nerdvegas/rez/wiki/Command-Line-Tools#rez-env) tool: + + ]$ rez-env requests-2.2+ python-2.6 'pymongo-0+<2.7' + + You are now in a rez-configured environment. + + resolved by ajohns@nn188.somewhere.com, on Wed Feb 26 15:56:20 2014, using Rez v2.0.0 + + requested packages: + requests-2.2+ + python-2.6 + pymongo-0+<2.7 + + resolved packages: + python-2.6.8 /software/ext/python/2.6.8 + platform-linux /software/ext/platform/linux + requests-2.2.1 /software/ext/requests/2.2.1/python-2.6 + pymongo-2.6.3 /software/ext/pymongo/2.6.3 + arch-x86_64 /software/ext/arch/x86_64 + + > ]$ _ + +This example creates an environment containing the package 'houdini' version 12.5 +or greater, and runs the command 'hescape -h' inside that environment: + + ]$ rez-env houdini-12.5+ -- hescape -h + Usage: hescape [-foreground] [-s editor] [filename ...] + -h: output this usage message + -s: specify starting desktop by name + -foreground: starts process in foreground + +Resolved environments can also be created via the API: + + >>> import subprocess + >>> from rez.resolved_context import ResolvedContext + >>> + >>> r = ResolvedContext(["houdini-12.5+", "houdini-0+<13", "java", "!java-1.8+"]) + >>> p = r.execute_shell(command='which hescape', stdout=subprocess.PIPE) + >>> out, err = p.communicate() + >>> + >>> print out + '/software/ext/houdini/12.5.562/bin/hescape' + + +## Quickstart + +First, install Rez. Download the source, and from the source directory, run +(with DEST_DIR replaced with your install location): + + ]$ python ./install.py -v DEST_DIR + +This installs the Rez command line tools. It will print a message at the end +telling you how to use Rez when the installation has completed. Rez is not a +normal Python package and so you do not typically install it with pip or setup.py. +Do *not* move the installation - re-install to a new location if you want to +change the install path. If you want to install rez for multiple operating +systems, perform separate installs for each of those systems. + +Next, you need to create some essential Rez packages. The *rez-bind* tool creates +Rez packages that are based on software already installed on your system. Try +binding the following list of packages (note that for Python, you may need +administrative privileges): + + ]$ rez-bind platform + ]$ rez-bind arch + ]$ rez-bind os + ]$ rez-bind python + +Now you should be able to create an environment containing Python. Try this: + + ]$ rez-env python -- which python + /home/ajohns/packages/python-2.7.8/platform-linux/arch-x86_64/os-Ubuntu-12.04/bin/python + + +## Building Your First Package + +The *rez-build* tool is used to build packages and install them locally (typically +to *$HOME/packages*). Once you've done that, you can use them via *rez-env*, just +like any other package: + + ]$ cd example_packages/hello_world + ]$ rez-build --install + ... + ]$ rez-env hello_world -- hello + Hello world! + + +## Features + +* Supports Linux, OSX and Windows; +* Allows for a fast and efficient build-install-test cycle; +* Creates shells of type: bash, tcsh, other (shells can be added as plugins); +* Contains a deployment system supporting git, mercurial and svn (as plugins); +* Environment resolves can be saved to disk and reused at a later date (a bit + like VirtualEnv); +* Highly pluggable, supports five different plugin types to do things from + adding new shell types, to adding new build systems; +* Contains a version resolving algorithm, for avoiding version clashes; +* Visualises resolved environments in a rendered dot-graph; +* Packages are found in a search path, so different packages can be deployed + to different locations; +* Supports alphanumeric version numbers; +* Has a powerful version requirements syntax, able to describe any version + range, and a conflict operator for rejecting version ranges; +* Package 'variants' - a way to define different flavors of the same package + version, for example a plugin built for multiple versions of the host app; +* Custom release hooks (such as post-release operations) can be added as plugins; +* Has a time lock feature, which allows old resolves to be recreated (newer + packages are ignored); +* Package definitions are a single, succinct file; +* Packages define their effect on the environment (adding to PATH etc) in a + platform- and shell- agnostic way, using a dedicated python API; +* Has a memcached-based caching system, for caching environment resolves; +* Has a package filtering feature, allowing for staged package releases such as + alpha and beta packages. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..1e59449c7d --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,44 @@ +# Releasing New Rez Versions + +If you are a collaborator and have push access to master, these are the steps to +follow to release a new version: + +On your development branch, before creating your PR: + +1. Make sure that the version in `utils/_version.py` is updated appropriately. + Rez uses [semantic versioning](https://semver.org/). +2. Use [this format](https://help.github.com/articles/closing-issues-using-keywords/) + to mention issue(s) that a commit has fixed. If you don't do this, the + changelog gets screwed up (it won't list these fixes). +3. Ensure all tests pass (run rez-selftest). + +When creating your PR: + +1. Don't create it from your master branch - always have a branch dedicated to + the issue(s) you're addressing. +2. Any issues this PR addresses, that haven't already been mentioned in commits + as per above, should be listed in the PR description using the same format. + If you don't do this, the changelog gets screwed up (it won't list these fixes). + +After PR is merged to master: + +1. Run `bash tag.sh`. This tags the git repo with the version in `utils/_version.py`. + Then run `git push --tags` to push this new tag. +2. Generate the new changelog entry for this version, like so: + ``` + ]$ python ./release_util.py create-changelog-entry + ``` + This writes out the latest changelog entry to LATEST_CHANGELOG.md. Take this + file, and add the contents to the top of CHANGELOG.md along with whatever minor + formatting changes are required. If there are any problems (missing/incorrect + fixed issues) fix them here. If you feel that further description would aid + in understanding this PR, add it here, directly after the "Full Changelog" link. + + Note that this step can be slow. Go get a coffee. +3. Commit and push the changelog update. +4. Generate the new GitHub release notes, like so: + ``` + ]$ python ./release_util.py create-release-notes + ``` + Then verify the notes have been created correctly - they should appear + [here](https://github.com/nerdvegas/rez/releases). diff --git a/bin/_rez-complete b/bin/_rez-complete new file mode 100755 index 0000000000..4f22af0fbc --- /dev/null +++ b/bin/_rez-complete @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("complete") diff --git a/bin/_rez_fwd b/bin/_rez_fwd new file mode 100755 index 0000000000..255085c0eb --- /dev/null +++ b/bin/_rez_fwd @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("forward") diff --git a/bin/bez b/bin/bez new file mode 100755 index 0000000000..b04a496454 --- /dev/null +++ b/bin/bez @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._bez import run +run() diff --git a/bin/rez b/bin/rez new file mode 100755 index 0000000000..927395c5a5 --- /dev/null +++ b/bin/rez @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run() diff --git a/bin/rez-bind b/bin/rez-bind new file mode 100755 index 0000000000..9e507c9edf --- /dev/null +++ b/bin/rez-bind @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("bind") diff --git a/bin/rez-build b/bin/rez-build new file mode 100755 index 0000000000..e6a511748a --- /dev/null +++ b/bin/rez-build @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("build") diff --git a/bin/rez-config b/bin/rez-config new file mode 100755 index 0000000000..93c09fa35e --- /dev/null +++ b/bin/rez-config @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("config") diff --git a/bin/rez-context b/bin/rez-context new file mode 100755 index 0000000000..470cf76f32 --- /dev/null +++ b/bin/rez-context @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("context") diff --git a/bin/rez-cp b/bin/rez-cp new file mode 100644 index 0000000000..0437a5b3c9 --- /dev/null +++ b/bin/rez-cp @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("cp") diff --git a/bin/rez-depends b/bin/rez-depends new file mode 100755 index 0000000000..2c00c6217d --- /dev/null +++ b/bin/rez-depends @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("depends") diff --git a/bin/rez-diff b/bin/rez-diff new file mode 100755 index 0000000000..182eb53cec --- /dev/null +++ b/bin/rez-diff @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("diff") diff --git a/bin/rez-env b/bin/rez-env new file mode 100755 index 0000000000..c4aa0ba7b7 --- /dev/null +++ b/bin/rez-env @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("env") diff --git a/bin/rez-gui b/bin/rez-gui new file mode 100755 index 0000000000..8b8d39c681 --- /dev/null +++ b/bin/rez-gui @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("gui") diff --git a/bin/rez-help b/bin/rez-help new file mode 100755 index 0000000000..a4def798e9 --- /dev/null +++ b/bin/rez-help @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("help") diff --git a/bin/rez-interpret b/bin/rez-interpret new file mode 100755 index 0000000000..8bfc645e34 --- /dev/null +++ b/bin/rez-interpret @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("interpret") diff --git a/bin/rez-memcache b/bin/rez-memcache new file mode 100755 index 0000000000..f70a3d0590 --- /dev/null +++ b/bin/rez-memcache @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("memcache") diff --git a/bin/rez-pip b/bin/rez-pip new file mode 100755 index 0000000000..62fcd87240 --- /dev/null +++ b/bin/rez-pip @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("pip") diff --git a/bin/rez-plugins b/bin/rez-plugins new file mode 100755 index 0000000000..d1253f7882 --- /dev/null +++ b/bin/rez-plugins @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("plugins") diff --git a/bin/rez-python b/bin/rez-python new file mode 100755 index 0000000000..37a04d6479 --- /dev/null +++ b/bin/rez-python @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("python") diff --git a/bin/rez-release b/bin/rez-release new file mode 100755 index 0000000000..e85a1f47d1 --- /dev/null +++ b/bin/rez-release @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("release") diff --git a/bin/rez-search b/bin/rez-search new file mode 100755 index 0000000000..8acee00a24 --- /dev/null +++ b/bin/rez-search @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("search") diff --git a/bin/rez-selftest b/bin/rez-selftest new file mode 100755 index 0000000000..2f7a250e72 --- /dev/null +++ b/bin/rez-selftest @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("selftest") diff --git a/bin/rez-status b/bin/rez-status new file mode 100755 index 0000000000..37eed769d1 --- /dev/null +++ b/bin/rez-status @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("status") diff --git a/bin/rez-suite b/bin/rez-suite new file mode 100755 index 0000000000..6970ff5d06 --- /dev/null +++ b/bin/rez-suite @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("suite") diff --git a/bin/rez-test b/bin/rez-test new file mode 100644 index 0000000000..06f984b244 --- /dev/null +++ b/bin/rez-test @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("test") diff --git a/bin/rez-view b/bin/rez-view new file mode 100755 index 0000000000..ce17dacc67 --- /dev/null +++ b/bin/rez-view @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("view") diff --git a/bin/rez-yaml2py b/bin/rez-yaml2py new file mode 100755 index 0000000000..07d98ca788 --- /dev/null +++ b/bin/rez-yaml2py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run("yaml2py") diff --git a/bin/rezolve b/bin/rezolve new file mode 100755 index 0000000000..927395c5a5 --- /dev/null +++ b/bin/rezolve @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from rez.cli._main import run +run() diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..87f9e28607 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Rez.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Rez.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Rez" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Rez" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/api/modules.rst b/docs/api/modules.rst new file mode 100644 index 0000000000..332d86474f --- /dev/null +++ b/docs/api/modules.rst @@ -0,0 +1,8 @@ +API Documentation +================= + +.. toctree:: + :maxdepth: 4 + + rez + rezplugins diff --git a/docs/api/rez._sys.rst b/docs/api/rez._sys.rst new file mode 100644 index 0000000000..10cd7dd14c --- /dev/null +++ b/docs/api/rez._sys.rst @@ -0,0 +1,22 @@ +rez._sys package +================ + +Submodules +---------- + +rez._sys._setup module +---------------------- + +.. automodule:: rez._sys._setup + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rez._sys + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rez.bind.rst b/docs/api/rez.bind.rst new file mode 100644 index 0000000000..cf1aec9f68 --- /dev/null +++ b/docs/api/rez.bind.rst @@ -0,0 +1,62 @@ +rez.bind package +================ + +Submodules +---------- + +rez.bind.arch module +-------------------- + +.. automodule:: rez.bind.arch + :members: + :undoc-members: + :show-inheritance: + +rez.bind.cmake module +--------------------- + +.. automodule:: rez.bind.cmake + :members: + :undoc-members: + :show-inheritance: + +rez.bind.hello_world module +--------------------------- + +.. automodule:: rez.bind.hello_world + :members: + :undoc-members: + :show-inheritance: + +rez.bind.os module +------------------ + +.. automodule:: rez.bind.os + :members: + :undoc-members: + :show-inheritance: + +rez.bind.platform module +------------------------ + +.. automodule:: rez.bind.platform + :members: + :undoc-members: + :show-inheritance: + +rez.bind.python module +---------------------- + +.. automodule:: rez.bind.python + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rez.bind + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rez.cli.rst b/docs/api/rez.cli.rst new file mode 100644 index 0000000000..486c8878cf --- /dev/null +++ b/docs/api/rez.cli.rst @@ -0,0 +1,134 @@ +rez.cli package +=============== + +Submodules +---------- + +rez.cli._bez module +------------------- + +.. automodule:: rez.cli._bez + :members: + :undoc-members: + :show-inheritance: + +rez.cli._main module +-------------------- + +.. automodule:: rez.cli._main + :members: + :undoc-members: + :show-inheritance: + +rez.cli._util module +-------------------- + +.. automodule:: rez.cli._util + :members: + :undoc-members: + :show-inheritance: + +rez.cli.bind module +------------------- + +.. automodule:: rez.cli.bind + :members: + :undoc-members: + :show-inheritance: + +rez.cli.bootstrap module +------------------------ + +.. automodule:: rez.cli.bootstrap + :members: + :undoc-members: + :show-inheritance: + +rez.cli.build module +-------------------- + +.. automodule:: rez.cli.build + :members: + :undoc-members: + :show-inheritance: + +rez.cli.context module +---------------------- + +.. automodule:: rez.cli.context + :members: + :undoc-members: + :show-inheritance: + +rez.cli.env module +------------------ + +.. automodule:: rez.cli.env + :members: + :undoc-members: + :show-inheritance: + +rez.cli.forward module +---------------------- + +.. automodule:: rez.cli.forward + :members: + :undoc-members: + :show-inheritance: + +rez.cli.interpret module +------------------------ + +.. automodule:: rez.cli.interpret + :members: + :undoc-members: + :show-inheritance: + +rez.cli.release module +---------------------- + +.. automodule:: rez.cli.release + :members: + :undoc-members: + :show-inheritance: + +rez.cli.settings module +----------------------- + +.. automodule:: rez.cli.settings + :members: + :undoc-members: + :show-inheritance: + +rez.cli.suite module +-------------------- + +.. automodule:: rez.cli.suite + :members: + :undoc-members: + :show-inheritance: + +rez.cli.test module +------------------- + +.. automodule:: rez.cli.test + :members: + :undoc-members: + :show-inheritance: + +rez.cli.tools module +-------------------- + +.. automodule:: rez.cli.tools + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rez.cli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rez.rst b/docs/api/rez.rst new file mode 100644 index 0000000000..533c304618 --- /dev/null +++ b/docs/api/rez.rst @@ -0,0 +1,256 @@ +Rez +=== + +Subpackages +----------- + +.. toctree:: + + rez._sys + rez.bind + rez.cli + rez.tests + +Submodules +---------- + +rez.bind_utils module +--------------------- + +.. automodule:: rez.bind_utils + :members: + :undoc-members: + :show-inheritance: + +rez.bootstrap module +-------------------- + +.. automodule:: rez.bootstrap + :members: + :undoc-members: + :show-inheritance: + +rez.build_process module +------------------------ + +.. automodule:: rez.build_process + :members: + :undoc-members: + :show-inheritance: + +rez.build_system module +----------------------- + +.. automodule:: rez.build_system + :members: + :undoc-members: + :show-inheritance: + +rez.build_utils module +---------------------- + +.. automodule:: rez.build_utils + :members: + :undoc-members: + :show-inheritance: + +rez.dot module +-------------- + +.. automodule:: rez.dot + :members: + :undoc-members: + :show-inheritance: + +rez.env module +-------------- + +.. automodule:: rez.env + :members: + :undoc-members: + :show-inheritance: + +rez.exceptions module +--------------------- + +.. automodule:: rez.exceptions + :members: + :undoc-members: + :show-inheritance: + +rez.formulae_manager module +--------------------------- + +.. automodule:: rez.formulae_manager + :members: + :undoc-members: + :show-inheritance: + +rez.package_maker module +------------------------ + +.. automodule:: rez.package_maker + :members: + :undoc-members: + :show-inheritance: + +rez.package_maker_ module +------------------------- + +.. automodule:: rez.package_maker_ + :members: + :undoc-members: + :show-inheritance: + +rez.packages module +------------------- + +.. automodule:: rez.packages + :members: + :undoc-members: + :show-inheritance: + +rez.platform_ module +-------------------- + +.. automodule:: rez.platform_ + :members: + :undoc-members: + :show-inheritance: + +rez.plugin_managers module +-------------------------- + +.. automodule:: rez.plugin_managers + :members: + :undoc-members: + :show-inheritance: + +rez.py_dist module +------------------ + +.. automodule:: rez.py_dist + :members: + :undoc-members: + :show-inheritance: + +rez.release_hook module +----------------------- + +.. automodule:: rez.release_hook + :members: + :undoc-members: + :show-inheritance: + +rez.release_vcs module +---------------------- + +.. automodule:: rez.release_vcs + :members: + :undoc-members: + :show-inheritance: + +rez.resolved_context module +--------------------------- + +.. automodule:: rez.resolved_context + :members: + :undoc-members: + :show-inheritance: + +rez.resolver module +------------------- + +.. automodule:: rez.resolver + :members: + :undoc-members: + :show-inheritance: + +rez.resources module +-------------------- + +.. automodule:: rez.resources + :members: + :undoc-members: + :show-inheritance: + +rez.rex module +-------------- + +.. automodule:: rez.rex + :members: + :undoc-members: + :show-inheritance: + +rez.rex_bindings module +----------------------- + +.. automodule:: rez.rex_bindings + :members: + :undoc-members: + :show-inheritance: + +rez.settings module +------------------- + +.. automodule:: rez.settings + :members: + :undoc-members: + :show-inheritance: + +rez.shells module +----------------- + +.. automodule:: rez.shells + :members: + :undoc-members: + :show-inheritance: + +rez.sigint module +----------------- + +.. automodule:: rez.sigint + :members: + :undoc-members: + :show-inheritance: + +rez.solver module +----------------- + +.. automodule:: rez.solver + :members: + :undoc-members: + :show-inheritance: + +rez.source_retrieval module +--------------------------- + +.. automodule:: rez.source_retrieval + :members: + :undoc-members: + :show-inheritance: + +rez.system module +----------------- + +.. automodule:: rez.system + :members: + :undoc-members: + :show-inheritance: + +rez.util module +--------------- + +.. automodule:: rez.util + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rez + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rez.tests.rst b/docs/api/rez.tests.rst new file mode 100644 index 0000000000..c191f97297 --- /dev/null +++ b/docs/api/rez.tests.rst @@ -0,0 +1,78 @@ +rez.tests package +================= + +Submodules +---------- + +rez.tests.test_build module +--------------------------- + +.. automodule:: rez.tests.test_build + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_commands module +------------------------------ + +.. automodule:: rez.tests.test_commands + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_context module +----------------------------- + +.. automodule:: rez.tests.test_context + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_formatter module +------------------------------- + +.. automodule:: rez.tests.test_formatter + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_rex module +------------------------- + +.. automodule:: rez.tests.test_rex + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_shells module +---------------------------- + +.. automodule:: rez.tests.test_shells + :members: + :undoc-members: + :show-inheritance: + +rez.tests.test_solver module +---------------------------- + +.. automodule:: rez.tests.test_solver + :members: + :undoc-members: + :show-inheritance: + +rez.tests.util module +--------------------- + +.. automodule:: rez.tests.util + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rez.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.build_system.rst b/docs/api/rezplugins.build_system.rst new file mode 100644 index 0000000000..995ab9ee49 --- /dev/null +++ b/docs/api/rezplugins.build_system.rst @@ -0,0 +1,38 @@ +rezplugins.build_system package +=============================== + +Submodules +---------- + +rezplugins.build_system.bez module +---------------------------------- + +.. automodule:: rezplugins.build_system.bez + :members: + :undoc-members: + :show-inheritance: + +rezplugins.build_system.cmake module +------------------------------------ + +.. automodule:: rezplugins.build_system.cmake + :members: + :undoc-members: + :show-inheritance: + +rezplugins.build_system.make module +----------------------------------- + +.. automodule:: rezplugins.build_system.make + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rezplugins.build_system + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.release_hook.rst b/docs/api/rezplugins.release_hook.rst new file mode 100644 index 0000000000..cfb6bae974 --- /dev/null +++ b/docs/api/rezplugins.release_hook.rst @@ -0,0 +1,22 @@ +rezplugins.release_hook package +=============================== + +Submodules +---------- + +rezplugins.release_hook.emailer module +-------------------------------------- + +.. automodule:: rezplugins.release_hook.emailer + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rezplugins.release_hook + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.release_vcs.rst b/docs/api/rezplugins.release_vcs.rst new file mode 100644 index 0000000000..387e0b5f20 --- /dev/null +++ b/docs/api/rezplugins.release_vcs.rst @@ -0,0 +1,38 @@ +rezplugins.release_vcs package +============================== + +Submodules +---------- + +rezplugins.release_vcs.git module +--------------------------------- + +.. automodule:: rezplugins.release_vcs.git + :members: + :undoc-members: + :show-inheritance: + +rezplugins.release_vcs.hg module +-------------------------------- + +.. automodule:: rezplugins.release_vcs.hg + :members: + :undoc-members: + :show-inheritance: + +rezplugins.release_vcs.svn module +--------------------------------- + +.. automodule:: rezplugins.release_vcs.svn + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rezplugins.release_vcs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.rst b/docs/api/rezplugins.rst new file mode 100644 index 0000000000..9ed1071556 --- /dev/null +++ b/docs/api/rezplugins.rst @@ -0,0 +1,21 @@ +Rez Plugins +=========== + +Subpackages +----------- + +.. toctree:: + + rezplugins.build_system + rezplugins.release_hook + rezplugins.release_vcs + rezplugins.shell + rezplugins.source_retriever + +Module contents +--------------- + +.. automodule:: rezplugins + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.shell.rst b/docs/api/rezplugins.shell.rst new file mode 100644 index 0000000000..afc5ecf4e2 --- /dev/null +++ b/docs/api/rezplugins.shell.rst @@ -0,0 +1,54 @@ +rezplugins.shell package +======================== + +Submodules +---------- + +rezplugins.shell.bash module +---------------------------- + +.. automodule:: rezplugins.shell.bash + :members: + :undoc-members: + :show-inheritance: + +rezplugins.shell.csh module +--------------------------- + +.. automodule:: rezplugins.shell.csh + :members: + :undoc-members: + :show-inheritance: + +rezplugins.shell.sh module +-------------------------- + +.. automodule:: rezplugins.shell.sh + :members: + :undoc-members: + :show-inheritance: + +rezplugins.shell.tcsh module +---------------------------- + +.. automodule:: rezplugins.shell.tcsh + :members: + :undoc-members: + :show-inheritance: + +rezplugins.shell.windows module +------------------------------- + +.. automodule:: rezplugins.shell.windows + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rezplugins.shell + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/rezplugins.source_retriever.rst b/docs/api/rezplugins.source_retriever.rst new file mode 100644 index 0000000000..f570aa200e --- /dev/null +++ b/docs/api/rezplugins.source_retriever.rst @@ -0,0 +1,38 @@ +rezplugins.source_retriever package +=================================== + +Submodules +---------- + +rezplugins.source_retriever.archive module +------------------------------------------ + +.. automodule:: rezplugins.source_retriever.archive + :members: + :undoc-members: + :show-inheritance: + +rezplugins.source_retriever.git module +-------------------------------------- + +.. automodule:: rezplugins.source_retriever.git + :members: + :undoc-members: + :show-inheritance: + +rezplugins.source_retriever.hg module +------------------------------------- + +.. automodule:: rezplugins.source_retriever.hg + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: rezplugins.source_retriever + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000000..ad6dccb9c6 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# +# Rez documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 30 11:14:36 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +sys.path.insert(0, os.path.abspath('../src')) + +import rez + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', +# 'sphinx.ext.napoleon', # this works with Sphinx 1.3+ + 'sphinxcontrib.napoleon', # this works with Sphinx <= 1.2. +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Rez' +copyright = u'2014, Allan Johns' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '.'.join(rez.__version__.split('.')[:2]) +# The full version, including alpha/beta/rc tags. +release = rez.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Rezdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Rez.tex', u'Rez Documentation', + u'Allan Johns', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'rez', u'Rez Documentation', + [u'Allan Johns'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Rez', u'Rez Documentation', + u'Allan Johns', 'Rez', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000000..8a4ea4cb6a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,152 @@ +Welcome to Rez's documentation! +=============================== + +.. toctree:: + :maxdepth: 2 + :numbered: + + api/modules + one-liners + +Introduction +------------ + +Rez is a cross-platform, LGPL Licensed python library and set of utilities for building and installing packages, and resolving environments containing these packages at runtime, avoiding version conflicts. The main tools are: + +- **rez-env** - Creates a configured shell containing a set of requested packages. Supports **bash** and **tcsh**, and mimics the startup sequences of the native shell. + +- **rez-build** - Builds a package of any type (python, C++ etc), and installs it locally for testing. Supports **cmake**. + +- **rez-release** - Builds and centrally deploys a package, and updates the associated source control repository (creating tags etc). Supports **git**, **mercurial** and **svn**. + +Unlike many packaging systems, Rez is able to install many different versions of the same packages. When you use the rez-env tool, a new environment is dynamically created, containing the requested packages. Rez resolves environments at runtime, rather than install time - however, you are able to store a resolve to disk, and reuse it at a later date. + +Here's an example which places the user into a resolved shell containing the requested packages: + +:: + + ]$ rez-env requests-2.2+ python-2.6 'pymongo-0+<2.7' + + You are now in a rez-configured environment. + + resolved by ajohns@nn188.somewhere.com, on Wed Feb 26 15:56:20 2014, using Rez v2.0.0 + + implicit packages: + platform-linux + arch-x86_64 + + requested packages: + requests-2.2+ + python-2.6 + pymongo-0+<2.7 + + resolved packages: + python-2.6.8 /software/ext/python/2.6.8 + platform-linux /software/ext/platform/linux + requests-2.2.1 /software/ext/requests/2.2.1/python-2.6 + pymongo-2.6.3 /software/ext/pymongo/2.6.3 + arch-x86_64 /software/ext/arch/x86_64 + + > ]$ _ + +Here's an example which creates an environment containing the package 'houdini' version 12.5 or greater, and runs the command 'hescape -h' inside that environment: + +:: + + ]$ rez-env -c 'hescape -h' houdini-12.5+ + Usage: hescape [-foreground] [-s editor] [filename ...] + -h: output this usage message + -f: force the use of asset definitions in OTL files on the command line + -s: specify starting desktop by name + -foreground: starts process in foreground + +Resolved environments can also be created programmatically: + +:: + + >>> from rez.resolved_context import ResolvedContext + >>> + >>> r = ResolvedContext(["houdini-12.5+", "houdini-0+<13", "java", "!java-1.8+"]) + >>> + >>> r.print_info() + resolved by ajohns@nn188.somewhere.com, on Wed Feb 26 13:03:30 2014, using Rez v2.0.0 + + implicit packages: + platform-linux + arch-x86_64 + + requested packages: + houdini-12.5+ + houdini-0+<13 + java + + resolved packages: + java-1.7.21 /software/ext/java/1.7.21 + platform-linux /software/ext/platform/linux + arch-x86_64 /software/ext/arch/x86_64 + houdini-12.5.562 /software/ext/houdini/12.5.562 + >>> + >>> import subprocess + >>> p = r.execute_shell(command='which hescape', stdout=subprocess.PIPE) + >>> stdout,stderr = p.communicate() + >>> + >>> print stdout + '/software/ext/houdini/12.5.562/bin/hescape' + +Features +-------- + +- Supports Linux and OSX; +- Allows for a fast and efficient build-install-test cycle; +- Creates shells of type: bash, tcsh, other (shells can be added as plugins); +- Contains a deployment system supporting git, mercurial and svn (as plugins); +- Environment resolves can be saved to disk and reused at a later date (a bit like VirtualEnv); +- Highly pluggable, supports five different plugin types to do things from adding new shell types, to adding new build systems; +- Contains a version resolving algorithm, for avoiding version clashes; +- Visualises resolved environments in a rendered dot-graph; +- Packages are found in a search path, so different packages can be deployed to different locations; +- Supports alphanumeric version numbers; +- Has a powerful version requirements syntax, able to describe any version range, and a conflict operator for rejecting version ranges; +- Package 'variants' - a way to define different flavors of the same package version, for example a plugin built for multiple versions of the host app; +- Custom release hooks (such as post-release operations) can be added as plugins; +- Has a time lock feature, which allows old resolves to be recreated (newer packages are ignored); +- Package definitions are a single, succinct file; +- Packages define their effect on the environment (adding to PATH etc) in a platform- and shell- agnostic way, using a dedicated python API; +- Has a memcached-based caching system, for caching environment resolves. + + +Installation +------------ + +To install Rez, simply: + +:: + + pip install rez + +Or, to install from source: + +:: + + python setup.py install + +To see that it's working: + +:: + + ]$ rez-env -c 'hello_world' hello_world + Hello Rez World! + +Useful Links +------------ + +- `CMake Help `_ +- `YAML Language Guide `_ +- `PyYAML `_ + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000000..21d8ea40cb --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Rez.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Rez.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/one-liners.rst b/docs/one-liners.rst new file mode 100644 index 0000000000..8f4c8edc4f --- /dev/null +++ b/docs/one-liners.rst @@ -0,0 +1,71 @@ +One-Liners +========== + +A list of useful one-liners for rez-config and related tools + +Display info about the package foo: + +:: + + rez-info foo + +List the packages that foo depends on: + +:: + + rez-config --print-packages foo + +Jump into an environment containing foo-5(.x.x.x...): + +:: + + rez-env foo-5 + +Run a command inside a configured shell + +:: + + rez-run foo-5 bah-1.2 -- my-command + +Show the resolve dot-graph for a given shell: + +:: + + rez-run foo-5 bah-1.2 fee -- rez-context-image + +Display a dot-graph showing the first failed attempt of the given configuration PKGS: + +:: + + rez-config --max-fails=0 --dot-file=/tmp/dot.jpg PKGS ; firefox /tmp/dot.jpg + +Show a dot-graph of all the packages dependent on foo: + +:: + + rez-depends show-dot foo + +List every package in the system, and the description of each + +:: + + rez-config-list --desc + +Show the resolve dot-graph for a given shell, but just show that part of the graph that +contains packages dependent (directly or indirectly) on fee: + +:: + + rez-run foo-5 bah-1.2 fee -- rez-context-image --package=fee + +Run a command inside a toolchain wrapper: +:: + + rez-run mytoolchain -- sometool -- some-command + +Jump into a toolchain, and then into a wrapper's env: + +:: + + rez-run mytoolchain + sometool ---i diff --git a/example_packages/hello_world/CMakeLists.txt b/example_packages/hello_world/CMakeLists.txt new file mode 100644 index 0000000000..d8dd8786fa --- /dev/null +++ b/example_packages/hello_world/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +include(RezBuild) + +file(GLOB_RECURSE py_files "python/*.py") +rez_install_python( + py + FILES ${py_files} + DESTINATION . +) + +file(GLOB bin_files "bin/*") +rez_install_files( + ${bin_files} + DESTINATION . + EXECUTABLE +) diff --git a/example_packages/hello_world/README.md b/example_packages/hello_world/README.md new file mode 100644 index 0000000000..7cf4729e96 --- /dev/null +++ b/example_packages/hello_world/README.md @@ -0,0 +1,15 @@ +Welcome to building your first rez package! + +This package gets built using 'bez', a very simple python-based build system that +comes with rez. It reads the *rezbuild.py* file, and executes the *build* function +within, passing in build info such as *source_path* and *install_path*. + +Rez has extensible support for other build systems, and comes with CMake support +included. A *CMakeLists.txt.example* file is provided; to use cmake instead, +just ensure that the cmake binary is visible; rename *CMakeLists.txt.example* to +*CMakeLists.txt*; and delete or rename *rezbuild.py*. Rez determines which build +system to use based on the build file found in the package source root. + +When you run *rez-build -i*, rez uses your package's definition file (package.py) +to create the correct build environment, and then runs the appropriate build +system's executable within that environment. diff --git a/example_packages/hello_world/bin/hello b/example_packages/hello_world/bin/hello new file mode 100644 index 0000000000..d43d5807e8 --- /dev/null +++ b/example_packages/hello_world/bin/hello @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +from hello_world import hello +hello() diff --git a/example_packages/hello_world/package.py b/example_packages/hello_world/package.py new file mode 100644 index 0000000000..99bd1dc487 --- /dev/null +++ b/example_packages/hello_world/package.py @@ -0,0 +1,26 @@ +name = "hello_world" + +version = "1.0.0" + +authors = [ + "ajohns" +] + +description = \ + """ + Python-based hello world example package. + """ + +tools = [ + "hello" +] + +requires = [ + "python" +] + +uuid = "examples.hello_world_py" + +def commands(): + env.PYTHONPATH.append("{root}/python") + env.PATH.append("{root}/bin") diff --git a/example_packages/hello_world/python/hello_world.py b/example_packages/hello_world/python/hello_world.py new file mode 100644 index 0000000000..9f8648f37e --- /dev/null +++ b/example_packages/hello_world/python/hello_world.py @@ -0,0 +1,4 @@ + + +def hello(): + print "Hello world!" diff --git a/example_packages/hello_world/rezbuild.py.example b/example_packages/hello_world/rezbuild.py.example new file mode 100644 index 0000000000..d7f683765a --- /dev/null +++ b/example_packages/hello_world/rezbuild.py.example @@ -0,0 +1,46 @@ +import os +import os.path +import shutil +import stat + + +def build(source_path, build_path, install_path, targets): + + def _build(): + # python source + src_py = os.path.join(source_path, "python") + dest_py = os.path.join(build_path, "python") + + if not os.path.exists(dest_py): + shutil.copytree(src_py, dest_py) + + # binaries + mode = (stat.S_IRUSR | stat.S_IRGRP | + stat.S_IXUSR | stat.S_IXGRP) + + src_bin = os.path.join(source_path, "bin") + dest_bin = os.path.join(build_path, "bin") + + if not os.path.exists(dest_bin): + shutil.copytree(src_bin, dest_bin) + + for name in os.listdir(dest_bin): + filepath = os.path.join(dest_bin, name) + os.chmod(filepath, mode) + + def _install(): + for name in ("bin", "python"): + src = os.path.join(build_path, name) + dest = os.path.join(install_path, name) + + if os.path.exists(dest): + shutil.rmtree(dest) + + print src + print dest + shutil.copytree(src, dest) + + _build() + + if "install" in (targets or []): + _install() diff --git a/install.py b/install.py new file mode 100644 index 0000000000..fe8d5e91c8 --- /dev/null +++ b/install.py @@ -0,0 +1,182 @@ +""" +This script uses an embedded copy of virtualenv to create a standalone, +production-ready Rez installation in the specified directory. +""" +import os +import sys +import shutil +import os.path +import textwrap +import subprocess +from optparse import OptionParser + +source_path = os.path.dirname(os.path.realpath(__file__)) +bin_path = os.path.join(source_path, "bin") +src_path = os.path.join(source_path, "src") +sys.path.insert(0, src_path) + +from rez.utils._version import _rez_version +from rez.backport.shutilwhich import which +from build_utils.virtualenv.virtualenv import Logger, create_environment, \ + path_locations +from build_utils.distlib.scripts import ScriptMaker + + +class fake_entry(object): + code_template = textwrap.dedent( + """ + from rez.cli.{module} import run + run({target}) + """).strip() + '\n' + + def __init__(self, name): + self.name = name + + def get_script_text(self): + module = "_main" + target = "" + if self.name == "bez": + module = "_bez" + elif self.name == "_rez_fwd": # TODO rename this binary + target = "'forward'" + elif self.name not in ("rez", "rezolve"): + target = "'%s'" % self.name.split('-', 1)[-1] + return self.code_template.format(module=module, target=target) + + +class _ScriptMaker(ScriptMaker): + def __init__(self, *nargs, **kwargs): + super(_ScriptMaker, self).__init__(*nargs, **kwargs) + self.variants = set(('',)) + + def _get_script_text(self, entry): + return entry.get_script_text() + + +def patch_rez_binaries(dest_dir): + bin_names = os.listdir(bin_path) + _, _, _, venv_bin_path = path_locations(dest_dir) + venv_py_executable = which("python", env={"PATH":venv_bin_path, + "PATHEXT":os.environ.get("PATHEXT", "")}) + + # delete rez bin files written by setuptools + for name in bin_names: + filepath = os.path.join(venv_bin_path, name) + if os.path.isfile(filepath): + os.remove(filepath) + + # write patched bins instead. These go into 'bin/rez' subdirectory, which + # gives us a bin dir containing only rez binaries. This is what we want - + # we don't want resolved envs accidentally getting the venv's 'python'. + dest_bin_path = os.path.join(venv_bin_path, "rez") + if os.path.exists(dest_bin_path): + shutil.rmtree(dest_bin_path) + os.makedirs(dest_bin_path) + + maker = _ScriptMaker(bin_path, dest_bin_path) + maker.executable = venv_py_executable + options = dict(interpreter_args=["-E"]) + + for name in bin_names: + entry = fake_entry(name) + maker._make_script(entry, [], options=options) + + +def copy_completion_scripts(dest_dir): + # find completion dir in rez package + path = os.path.join(dest_dir, "lib") + completion_path = None + for root, dirs, _ in os.walk(path): + if os.path.basename(root) == "completion": + completion_path = root + break + + if completion_path: + dest_path = os.path.join(dest_dir, "completion") + if os.path.exists(dest_path): + shutil.rmtree(dest_path) + shutil.copytree(completion_path, dest_path) + return dest_path + + return None + + +if __name__ == "__main__": + usage = ("usage: %prog [options] DEST_DIR ('{version}' in DEST_DIR will " + "expand to Rez version)") + parser = OptionParser(usage=usage) + parser.add_option( + '-v', '--verbose', action='count', dest='verbose', default=0, + help="Increase verbosity.") + parser.add_option( + '-s', '--keep-symlinks', action="store_true", default=False, + help="Don't run realpath on the passed DEST_DIR to resolve symlinks; " + "ie, the baked script locations may still contain symlinks") + opts, args = parser.parse_args() + + if " " in os.path.realpath(__file__): + err_str = "\nThe absolute path of install.py cannot contain spaces due to setuptools limitation.\n" \ + "Please move installation files to another location or rename offending folder(s).\n" + parser.error(err_str) + + # determine install path + if len(args) != 1: + parser.error("expected DEST_DIR") + + dest_dir = args[0].format(version=_rez_version) + dest_dir = os.path.expanduser(dest_dir) + if not opts.keep_symlinks: + dest_dir = os.path.realpath(dest_dir) + + print "installing rez to %s..." % dest_dir + + # make virtualenv verbose + log_level = Logger.level_for_integer(2 - opts.verbose) + logger = Logger([(log_level, sys.stdout)]) + + # create the virtualenv + create_environment(dest_dir) + + # install rez from source + _, _, _, venv_bin_dir = path_locations(dest_dir) + py_executable = which("python", env={"PATH":venv_bin_dir, + "PATHEXT":os.environ.get("PATHEXT", + "")}) + args = [py_executable, "setup.py", "install"] + if opts.verbose: + print "running in %s: %s" % (source_path, " ".join(args)) + p = subprocess.Popen(args, cwd=source_path) + p.wait() + + # patch the rez binaries + patch_rez_binaries(dest_dir) + + # copy completion scripts into venv + completion_path = copy_completion_scripts(dest_dir) + + # mark venv as production rez install. Do not remove - rez uses this! + dest_bin_dir = os.path.join(venv_bin_dir, "rez") + validation_file = os.path.join(dest_bin_dir, ".rez_production_install") + with open(validation_file, 'w') as f: + f.write(_rez_version) + + # done + print + print "SUCCESS! To activate Rez, add the following path to $PATH:" + print dest_bin_dir + + if completion_path: + print('') + shell = os.getenv('SHELL') + + if shell: + shell = os.path.basename(shell) + ext = "csh" if "csh" in shell else "sh" # Basic selection logic + + print("You may also want to source the completion script (for %s):" % shell) + print("source {0}/complete.{1}".format(completion_path, ext)) + else: + print("You may also want to source the relevant completion script from:") + print(completion_path) + + print('') diff --git a/media/rez_banner.svg b/media/rez_banner.svg new file mode 100644 index 0000000000..f97c69efed --- /dev/null +++ b/media/rez_banner.svg @@ -0,0 +1,96 @@ + + + + + + + + + + image/svg+xml + + + + + + + + Resolve it with rez + + + + + diff --git a/media/rez_banner_256.png b/media/rez_banner_256.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf9cc0337142b9bf6dede801e658c2d492797b0 GIT binary patch literal 21733 zcmb?@g4B8!1aiV6b*gCQ^XP6Gx8Rt^RR1cQtSydwAbfd}{o?3ae%U%Y&Qc29TKfEP);r1iWsU2VL4%{{DPe0_b{?Ohx_EzRAm z*#^EkB8uoJNT2NpK=^`b>DWd|2&!eHr?snX25 z0tQ$aOV%G@A_%>>yxNwy1%svPmK=+*Fd#gw@BK!EA&b zlUSTZaVDx5dXWkZd`Im}zY4Oh;(QAu1X_W$WF;mEGeC1te^vmKe)*z-6ogqB^V~BV z9yL&VDs1QG?;?Q>FYyBvM-DF8zqShD9<~D{H1tfRdNrRLR7ZF-8P;NPsspp0iOO=` z^%{7u`GFL?2ABs)0giJZ?2&a5>(bS#d!gu~pjkKQ+>$d?kahSv99{xD5}brw2-_bC zp+$nXh`3yElu5m-_NSE~o&VIrM|_q1_q(um+#e^j5Onm3>i|SS#315`gG{Iip2e+6 z(#vtXzN7Y!K^Qa==nHEAyVAHz-p_bL1G6n>;{U! z!sNp86!$*fn#8<3LF^rM+m9->DGV_XJG>0=dr6HPe&|-&sZTf2E8ui)kZ|PK-Qctc z`@PJV%ZD*D=2+Zps6<0eVMmzf+?&jl4_Z#>7dtae#9-YJkT674!O6AkE8O zLvX-_U?J*NkXXGL;+6s#vhXa%t`Yjd-!5!mIgMD1`0&Rzw>NIIoJ<>zIF0p`0=}fe z$tO`SHoQ&?Tb4TtMgg@F3eW?;UF{F=7=f1TiW_z>yr||Eja(>-iv`I6qe9q+uLIz( z>!87K-8{;vh`@beH^aW8z66tPk|91&Y9n;Gapif~C1cZ| ze0_+0r1pt#{oNz`Jjc->GwT?B19aHlt?I%X53sLA;4C1*H#tS&wMe^xY)jbeT*O_0 zI0ZgT7T*hBy;nuwl}5LgvQ>=drShrU?_y8uUhXaSo(ke%GJGNkqcVZX{@;~iKCQkD zh9s!r`Ig+SJ81E~`q3On*lPXExdo$Fs~$g~C=X6}o7zgUC>RW}~!)`IfTthWce_P2{rNCu{PqbD*uiOKTN zWPlhPRP$QwT?ebEe|2`tiEEL$H{HgFd%ZAx^dq|H#I+=F@9-iq;;_GaTf%w_)Ur>S zF4VLfYR?x&79S%$%4HGHyT~`NeGQwLGpz&TV2tH{s*RP<$Nh(dZ^?A7~P})L`SYJ zIv`9dbG|{nCUcYPUVv45%lf0+R*Q4*2REz*=Wym#F)_#b`Y7W>({3P>Naq|%v?*-X ze}eO$ol;@#DGx1rL&0aaCW`jnVIr#Rwn=y+D1^?buF-@#ol8GG&odVr*NJvOIam-b z4Murv_-EyjKYNI2{XAzLm6lva5eyGr`fmG?DS&im!hKE6vUc4~f zvh~>m+t~0iy=ss=zclEF$K%cQeXX! z%w$g3_wJ*_{hgCPro`z9_rQ$=`mQW$*kS8t0ekBq!6qzx%gGGG0lTte`2?(qjKx#6 zZe5hinxnld`86G!iJKVTkTS^iNhO)6Lp3Au+i+Xh{f&XNGXS$r888 zFin<{vo2&77smtfw+iYUV7=Etr*dQy8(YCwU7#eTDAxedx|drZh-#}C_^|LcU@$!R zZ&zAXhS|s7KWnkXJ*r&WS4{Z+LNA=l&Q1LFFE3FJ~!QV21FijM?g$ zNI(z(4P26Dsq~t!dgF=FX}HKcfxH=YtT!j|0f0B{9d(|D;2qj4K$-!8C$B3F9|f)@ zYRQg~5=X8LBG((|hxG0okf_9_WNfnrlHJWszld<66Z03P({p%FE^Qf$e*)1em@VO* zcH5@BZQ#?l|M4!I? zrr2l)Rb@Sc8iqE)`$JuT?C5$#A7*Agpt7=TC6# z2I0p5sHUmjn_<#YLbV5M18| zIq<@K#Ei3R5nNxw)jY7%eHJvdXmpC1zL5T0+=OF6DG%8V5n&?-^ZCq4@+C1~L!e3i-P=sqiGqIaFs@eD`*EgX>8w`cny~>|z_{$U zIdAiA?Jgc!h*D>GAyj*&Wa0a_Ni8Dtxj^H}ppF#n6Yuu-h7o3kphFuVEAiHF5zJ&`h8Mow9{7gy>N7i--++#`Ckm4#bG$wi z<+hAWXUR9!AL`B9D5(}qO42k%xLYLOf%AH6Df$jWg5FBCU^QILxEz(8dZTru$}Nx3 zN@UX0`l6msZ6D=aI`ZWGLUC_E0WvmNQ?x>VH=I+nn%^wUyaH$K4>1LleAO|vK;pq> zNfG3e+I=U5IGc!4cTi^yVClZ--aL=X*Y>EjNJZG-X1_LhdF?R0cd5*$F(}c!V-sPv zcrVudQ+^GHcVaXuf~|GD;x=3wapSA;NDRd}VQr*46X;Paf{6&Di=IHfaF>BV{zI1r{?wK%Ut!uS)I(QlTe=i-;y zp6BC^RYZ;}^4Sf0n^$TgQ(TCZ|L?$vu zIr5`=C{_LKD39xfIe)KR4FiyaVSm~d0&Bs2A)!RHRuwG&9ISFZE}8s6`QY*$d?0}= zuSE%`X-BDR5j=O@LxM^g+n-568+Q2q9D|RrTmowqKVR_e*Np=8n5MUxQqAubo84E5 z={iSqUiwqt%);sjdDKOd1cMC=u6L0_K@|oWb%?uXODl(P*{k?4gIm=OcsYnXH z^TY1xl-5FB{XXmKiVFOw(?x&vr{H&)4tnPNZ}wybW;m1fNEm^e5^aNs3^E;AAkG`F z__g6O^DQ2c1xTAQvo1c%Ogg5$Z0Ro26<&jX6x4ieQEm1EN~SxjW^#}vMx{njSzz3T zWZyxNmmLovPH?wjnfItKrQMvMN%i5?m`4q~H?qKV+=u@*B}p?HwH=o0$GbtZtIg%% z@x(L#b+%?!lX^N1=E&;M!->f{;=)+tK=i<-pS$Za%_F~)4{{US#biSgW+|NUQbEZn ztRqa5mm)m%GYQprcGJD{sTbbyq@u5n6eCKAm z91OUC-T^7PV6TGMh8r-ECqh5skdcnm$KU%Cf@KS45~u`Z=%m?HL}aBQ9uY%(Sv?`v zk%CQYe%+8YXo5Bf)J_Kh?jf(yA|3;<0=cVLJIp zlGwi)E%7#XT_D?A@c}Oyo^vw~SM@iSnV9N-Dtv6IYBZh`S{{#U8$47W)m34=wkQvo zX=RXnCzbUve`UuM9+RYIA$+b?Ch0((Xywr8oqNBqNMK+7!B+o z${On(-McMZX4W@7n9)~Img!PE8}|HaiE4GtcR9;^tYVUmIZ6+;N|(mGg^4@b1>il7lnJK zMa|oL>o==n^?agB80mQ#&$M2uFwHH*FjqFoAo&P7aM*jj3-C$hGSy& zi`Z=uVGbU(-w*!Jh}pSR7~h{3>f5H)64}QG=992KW#Eav1rk52H ztuU`ba>*jy+5+Guk=dFU>;yYIt}R;d?0O~tL8X%CrhK*^%?~!L${IjN$ye3K z!V6MOATe*xM9Qm2mnaSgm`eiV1ik2EC>6MHCH#%w*U%?EMuG^Dy`Kyc%fK4S?fsTO zCL^=5ixAy15(4J**d-OO?^NJW*hY{b-yWXadr;HL-L9+WmUNI7Iz^J z%3IWkqA|AZInl;GJ)z7@yCgM;|2zj@O1zc3STP?E92HkC`yTnr%>p+uvqz}2g#_Td zr<4ou8tB~-sFOaaKn<-VJbQ@^6gI3PyvgrxUOyq{xhIH%-&l3_q}Ir~7=QS;gqZ&k zPScn?kS8$Ppt_4QUl+jB$+et7@rGRPMHo_@J$Ew6PNvEvl&H0y8#*~d;Lrn&aO6Cp z6_6a4c;^y;AjnR9t#p6`2`~mN<2_hffAG}UIS9F zSRm44tiq|4_nY<&qTT(E@J1i^mb7|=JcAM~jB8aVZSKxmlR=1>Yb_ANbi#eWZ;%|o zc-~0|g{8A*f2X1-I@*sLR@vk>H6k+ApX#XDLc26q^iohEf_kOL#xHr0$TETkb~jq< zF;T|DC3oyFYM}2lPM(NYM{Nz?9z92sBMBoh_u?7nGa4O-N;gO}AR5RqnlT&E=+MSz zMT+r&8mLEqalRl8Lwz~RqlV}g><=`;tUp^?I~)5e-Aw#SbUK3c_%0`w|cj|9I2|OU($_FPVoS(@_vKa%p}15j3@qzdf~t@lMdQB~MA9 zHj=4?AkDD~X@qR|3nc+F5PYl>2*FkmF2Of`|=84^fgSvB&dr4LXc0S;Az;ffTd)J)Ei@N;=)Af%axc&hAy+%8|?2g(W zx)16AYZGc}R8yqXn>R(H(pugO-AX+Cei4~sEy?-?;Ve`^1cR+r_Qx>tlF) ztZ`=cKZ1s35e?VAu|N5G)3T9yT(e-1Qf#gCRI|nSyxN;QGqI7h`0s9`Yu{@16WcS9 z;hQaDKFQfnqWcbSSG)PYEExJp1OFKI;%Q3&6&BwVha?L6{d9 z|DnhvO)};br;Neiv9VDt7qg02YcWI!Z8{UH*kSlLrvW35cJ$auUUc`F%CB3iwDNj8BH4|PumO0V!yLA7A6W8~ifE2dT z*rUd~NuSlfEgK-ewnqj6SY==cq{7L))p=Krx!fXMuqckv3awpK0Zphc7<7qNo(yz8 zz6S`)VI-uroI$*OU6~k%7=4oBsjhhgD$5Y-bJ3@QEboV=o>p< z$gxUJz}zWy^kZiL#EPo;TiN)f!bhq$5ip?9WL5|oo#vd{vebsgCmx3d5M6R3f*oV% zO8W}izOJ;LWBim3Rgu3lQ01v-cE?QW5nc9Cywg{H!Lix{cWd~5xYvuXNeJJ}wHYEa zAyRb+zC1Pga|l|Y!6Fl4svuEH{H73Q{6)xQ%9g2?jT#U4?aI}0sDb`z%fshUrfTHc z-Jd&$?iROUKh;LwoqthpUtU!{=fFk#1fZG8pBC}h-gQBuPw`tGmXJoG-f#?3cpy9L zW9r}1?W$qiuH^WPfm%DlWqo*iEnBG{I#)mwgCVg!x|`XDy|Rnwu@J9~*1)Spq8f|-~lySy!o z0M=oDN{JKCEOaJdB7QyB0Gj8R!mHr6>iFfVzfGimL}I4`RM$#^^`DKC*eoa0K^99R z;LDL!0y)CYy?yu>P*(6*+A-q5erxprS&HKc$nfjOWKP_!Fvy@T0uY}`A5E*5Q zzoZ$8p$O9g*>Q3*b1~pwCNp^WpPf^df10vbMi#?ntsN0^JTr+6Fg_{-YR2DbS0mcB z%tJ<8R9;-Iu*4Jqg}Qd4twM)Zy<}WYGrL&)XA@OkXtHO8CgJBpmfY^iW9d=l-rD^x z6}OgeeKTU=n7Xzv7d}0`K}>gzFdYM?P4+6h(Z$5LD$}@(kvC_cP|rN?gwZ?gJIShB zb=}dyW({4tjNM6U|L0A~IZy42?;VV#am(LhpaUryz9oP}mVO#@VhTfGTW$41Ch5@r zdKXc@M&%K`sCa@10@rrtWD9e@(XoFQJ$j)_J!c23SExXeG3B7xdrWR0kP{qsgj2fh zL1jCKV{LWBJ5n}mAt`e)C!!cT&W1xX`Sj!nSlMmK1oN*NHCX(P+BA4Xg>6k^4CQE- zjh?(q5f$bI9o6y%`oRicmo?DJ$cEcmmru^J%^F zSVUyvoK~lE4VPWkRATFcg4bD%cWFg6t)xN}A0Tl5ov)Q_E`kP*R!1GS(7+BT9-N=l z2yaKE3tvjU{G48Sc2CIT5=&W-Yu$_1!q<^Ku%kF1Jg?mEa6Ns`=67tNx4Zn$HRdn$ zsr4zpQxWK*qkGQ+9hz2#YYgwjNVRdM`{=K_sm;prrlp3n1K(_yo8qoq-6RyL{n(!A^s05~3oNa`QQeK`M{o3-b#5-=*4 zhLGD0Ee~`q0AaQN=M*BrIi?bmVwIN-8FVyyRlrd z04p}h2t{|w1IJ=}b63B+JgmI;)D{$FB zD!Im+u;@0$Nh& zkBhr^v$3cgX_t>oI#UtOjmV5%#1RFjz2d@Rm6NGGHFi4qA{%3WD!LadRu6QtaPY9E zh#a4{oX5O;(MQ9M^t4duh;A!UD1AL&;3fa$VrbxZdjJVCVIhWSoS26PP$WmjUUsHt z&CB}VcN4N%-X>Vrcu&^%&Or}1GHSiAT>-Q{rRheWrbOnFJ9u+xMpjNUAV!kfAAacV z(bjyn{^ZLt=4ecS>%VyvXiN}N0q0hd_0J3+3&hy@7J`}TK(!MN_8N|V%D|svyHu+3 zEMh2=?19eAfDob%poMQzM9~V9kxc)%Abt;!U_~X<`9&F~ z#l!thJtL5E%JUS+pEV*t!jkHv45=u~ctPogYy4=b4;WKi!P;cm7H*yn?w>qRcT+C+Wx3pLWf zA&LJ{ZA9?#r~QAk07(Z7;V}oeqsCruN)d?by8@4!#)#d1s8%#Zt@BqVo{skzUZSVT zn%s9(%oj`ye-C&`pB?X5lVL3+hGVK>j?7Jv>CC>XnEMDCnJzz*Jv-&!j`U&B89vD{ zR;+tpibDT-Lb{*~FZgd8WG}(&1umo)C%e{oNO6Z|q(&-Uq<;jMTn+*|NHal%?$fXR zWz$^LXPyqeQ1&AM32dM%b$$&km^&q~Jp9)9V57S(y1&A$kUO86#_ zE7_!$x--v(yODvgviC?Cc`s1r0{AOIpqa$J1KVo5e}d_|KTc!8&z?M8Vg!LXMk+g; z!QY&oj>ZtIF%BJC{Em*5T~y8%>`&YOyXAmCs_35%x>JrZE6faaD^1IX?l@#P(v`tz0@4 z%xlx-S4}JoLYW!!K?Q>TNSY$$ypvyyM=WS_0 zw%;4aNnuCYCq91%MUF2G8_f16%-bCQIdt_j2N?fcoVsa#sPX%`--mzeAI&9T)7{Ka zR*tnRoja}31ofT*Lg1SDtK$CNcqizw8=DZW@>3lD@8Hqy=#$a0;Bg`EccQj!99?6l z>S=sk6@Uh6y@Y?_q|fPGt`^SZ;-%G-v$8+hVxpIUu#a|xjyc3ECf2}ls(l`W1pWe;Z(Zd zjg;eFIM!7lhuDMFUfIzcbKDyUz)&{rk~9SWaDR#s^QeEmW??FuwOtB0=++_C33Q|h zI{wW7aK~=WL1-Z%pw5$j2p4X95jeynyojvRb7JuRqmS1B6_8AbGnb*(Zd_j$J<4{l z=5&<(Z20?u<1s%Y>1pBj@(toDAH~zJ|CX0r7y~)B&Sdm((otKbKqo6gTlp#JNJQzx?`P<=eOD zYQDI^_Em&W<`8Hd07W2TkiZQ&;CV0{k9A8;VS9GpxAxv9bl&GxY}r}!f3=d_Oc#17 zfZ_o~5TM?|ufuhbU79Zy+nZWr9b}@A2@V`jHW4kaUfbNmDKsW5Op$vQWjM=v#z(VP z*=}l98(SwMo?;2tRGwiXH8rJ)!-v(=&xTDwz@d1(~Q4F z{hPB1NVArzfvtt+w1DTRTA8b9AxyZR*^t9IIe+r9*@M%x3-g;qWx6Kr>-QYFG>Bo# z0<4lnRGAk9f6sW5dipeZw-|O_MB6-h{*>XX@?B`PhuqFU<#qq*@K;6!NDXzp*ytz} zP2D+o4UyVen}RtRGw&R`7_007sA1_!e__&pP`yR^G^*_`CuFauXfS@$uV!lF z#}-tcP!hUOXkF>~Bx2;qC9_ZmUEd?q>yUGmm;gFxiP5P*RZovjLiMeXS)3aFD1nP~ z;hp}yJrT>sp2Zka_V>;z*t4NBx5Ul+Qh^uT(DizqXnQJ7%?5PN9z*jMEIzVOWLb0u zlys+&j5!uW0d6PoYt`X6yIrEfD#pto=;0k1mDr|<`A~>kAnjL=f{vN43xgR4Q9r)ir5sC(l>guFa3Y?BI$0|G;_b?&Zs6@vN zcmQBo>;2J_bUFq5R`3u*X}UJ2kr+hWQ`7w_D%g5V)>xRo=EK6VOMZp&bBMiu?Zc&t zXlO%9Y_CLl3yL{@*fEfyal+Q_8$T0W9>T2=eifnk{rlfUQ~0&rQpj#E7Hg~Kklr4E zM_(_m%|C1T6P3)=GWSjE3IV+sDMP`~W-Uw%ZlGbwK(5tyO3_Cwim4jwW4QX5a`|SC zK<1>=i+B=0BBg}opcL3=Ex|Ww>M`&#%q&P+a8PjwBvx)UY=752EL3Mvdo{zJ*0uZ- zc(k3~QPQhaX=}s+A2y3oV4i2CBVEGs;mcHulLS&yI>xPuOf@SHG_vee{lDR@Us%qV zU&%IU;8Z4gO_%}bzMV4eNB%R5L|ExpkpeWBch7;IrQKC1tIJ#rbzrtpugg%XeGrl7 zZm;5j{%ztj89HNscnPb(qh#sCwZTh?`S7g@|L+2g1*EKfP*GYpYaow{=6(EvruT9^ zG*p+j7Fw3X0meJE_9f10K4_B$#6 z^xLl&%s#S#G{b6dFOhmew*r{1)W*%2BDeCh>qUs8}Ovq5Uc7w0xXPv(8;QGD>xql3hdVH;4O_EqW=*PEUOE zPuVYALEmx&xW-sxd%-hi#buEIPXX{ceF@o@d{7vo!J`4kP)GA2DMb{5Ytvuoq!bn> zn%YZYTd0Np*VsPK7>Cds+bNjM|SChV1i@sNLc`pC+#y1m9E;_lzXzfZ2I82eA%)pYpqnR`~ zF0|jXka#MIO~h=J9CtkY1*tYB{e*BV&U{$p^vSQgMNYhS8~Vzx%fZ%=*K3xH{YUL` zk}krJ9WsQXb2%s0on#TYCdjTY1Afn3a~SK_>iTUb8E$?iM08F~t4O_3ZuoA9Zm&*V z@j}uUq@AVhTBtK7d5M05dgeuZxYxUDqOz0N3!Ky>;1OKC$^C2y6itrZig*??g#V`|EsX=Qf)x}I#!w>7Sm~`n21m+*9xbo5GEF7y) z;=B`sMYMcht9y+~dW>JO-qUw?$sG;&x7hLSILXu$B5)1_@M%=Ln)~~1ubf+Ru=sUEgrF5SzvY+KhUuc zgTjr*!Bxdaq`SV3&APn*sqKUx9%GOwf<7jgU)ZEMqvw6f}136DoNAk`UUfb zuRi z$O>W}R16EjAVZEJ?}p5sckK{axi_asYTrx?vF155-n?5dlC&|xlOtl9c@}ON^qrn3 z-i|V)Z{4VgU+YLey7C?9yUsuuQi+Ht?mKr;g4!z4BQ6MG_JlCKNw3{f@C#;G70}m5 z2yqNlfl22bp=EKUaf_};T;vhW9IYCmSYlZj2)jS8!|t)T6qTKVc^FD)gN4SE`Mc%~ zXxvfg(q^&UP*65efq>|pU4*d8XO-u9`c@mhe;JRlB6gE^ErJlW2kc=M|ELGE?k#$v z6_e@4bsF>7iqKFUeq?NH1y(B=FL;CvChEa2JB86CP=54%Fh_6oVG#b>LvV|QW&$!v z5<7$+@0czUXd;gHCSP`_%n0e7LyefpcV{<>lO=J60cwixlSlnp36t~Dx|8pKW<+36 z#n+Z&$Eml6r|hkmH)Z1zXi+|k3Ezunj<{~#%$>uXW)YLS3Tcr?oq&oJ6MLW2N8(E-**{8GgFCc`B&H4jS)SKg(uE_$Yd?;FBkH?h*7Kw(tTOk&KtIuO zY5Q5~iO-{r^1*5G=b7GClJMXq?q~vM4PprFA@s@!2Ect`!49ydEBRMVkbEyi@q;Ns zT#kJ~lZj*JqN+<|M@dCTM^!BG;ikX^IHOq7PYJ=MCBMEoU~wX zQBq(Va}@kn(|S13x8G{?y4aMct~YnC4(cfM((hv2+zQL0U6o0e_Y z4tN(0=Tp|`ODci6&DjNRZO0O5VenuptCljMf*pEI`MQ#$-T9yPMyarqhI(;%hhqZw zaI_t0$dA~Yujwsc)1t8FIq^SN#h+avEg8@snAzg8ZQ+J~ zMiO>}dq&d}#;R@+Mn_DLhK4S@aU1?hqm}eI&smHspygGwSYBjNjljzKRxd-r2Z642)K6v-z_u}07 z-@^*P#^^V$e4!0x)g?8=r`l+g{5+js7(OGID`}xpx$Ab&GXtv+qI!Uf?Gf4F<- z`fA>Z0+B$BZ_-Nt{^`a$dm6)DQW|EEbdnEUCl_w*@i~rrJ3}0n^Ru`h#bwbSl&VWE z%1$rZl2H;#|Edeeb3CNS!cRM{Uf45^0_&E^!w&aR^XkDdpg@7{0kNN-wJjlBi3@LF zudR7D$X~C)_f$rp)#~R53?77C>AS5d5q!?{ipana8iMY#r^V{lw49c}V+qLeZ$xt9RnyO-ceSH(lX691AJH(T*{>mh{ zZTCCzu!pZ}xxY^ISVTSHEGh{h{u*qa)ZJk)P0@nTd`6pD8yCI~CC za~pb&`~;b&zC1tQW+TWNo+?7!&|=E7QYt7WG91d7uSI4B-s_t)h?-anT5N35wT@TO z5z=X9e=(KSA}kN)=&|7=uT&Jz8m_-Xn-v})6i!g4&Tu%^he(%_;fM7WV#^gfJO%OA ziaE8JIW1hqL9mo$3Ec4#;V9z=p63NqaaxHlDcO-Bin`?jY$I2hA0Yar)o`Xf_pi4^ zw9$#gOF$a~)^odsW$eQdUt9_;Q?!<^@OW1u(}T41e=}a;pcPNZD_K(|>jh&?ve%iF zZ=a&$anBp4V+is0lewe0O%QF-lsc@7m_`;6j`HF0Bi}#R4$j(m!@BVgQy9eFaf?uR zUpwaU^=?mB*%B%xlnRZVz+FPfp+Q>i%gr|ej51IHp1NBnW^U9%N3&KG-*5ZtLD6hm z$o(`|hh9=}@|6~8?K8&%6xe*5%fy{wxUv(ZZrrs!Al=#l!FDmujl(1EvCO5~iZTa^ zu0-4*@Xf}|2)6~3?^IyvSOCq3P*0Zqz%E#As2g_e>S2KZH_aQxxdQ`KBN-#|0eMFP zKJBC8$YTth%r2YsdQOH0ZkKK1Yco8P`p;%!t5+2!uZA_68rKt_4OJ|Rs~9fv%NIQo zH9ipE+jdwM_Lo~QOWfl)Z2OW5I#}%csiXsA=^V4GVm5J2cbY4n!(l_}C)!B@uaXNP zr6|(H$Y|DgZpMs{}~#H`Ud}14{sQ*>jsibT(2yHS7z;UBz-aJ z-Yq`8!$!s`R_~uO1QavoP7$mSXMb|F4j@work^453kwQ&JxS>v1_)@mE;G*3+_0y+ zhGxSP^$nEx%qrl|?A^WMYmZ6w6dhPM!Nu}6{ZCw%MLLL;$bAd8i`{fJ(Fha{QWT_O z7b6FoG94XbTH(8@XWpZncqg4gVWAlg{rWJG@%Z#)N|!}`4G6mfSdRp)kj4+4`lqc; z19xwjpT0`Tn_f(@Ckwt8;(l+_ySL`XqcB? zOv-oqih0*<0b^QZbMZIZ&gcR}tqgMyO!IF*=>!ooa<~s!eHL6umq|-*az>ELQzA`$ zLauxU=xdYAolGClas6yy9U*;8tdr``KZ)^TLzL02>~nY%>IpWkh)+&plrcRV4dylJ zf;F-!Mqj&9f4R2)xH`{Bth3EQjQ1e5|ADLVdT$qIb@3_l1K| z$>p{}MA?4^H1uhujaMvc)cmxU^A~yQ#8yClyE60U6q16+S8tc{N;q+^Mo?d4wT|sEfOaNC(Ck<*^?#A4PM-F&N*Y>c7LDPvi%in zaYdNKtj!?V=V_*)e{61Ax z+#dyp?e;xX&wf>C9(=l^GPYaiI6ANOuVMQg04~tnr#@n{u+uPM3Q6=q|a|)gh z1pAshgv>1z%MT{$wYdmb1EHaCoa>QN3G2ZGo@Jg5WD12U9P0D(B^8xJmjS%Yh!a0M zFu5%9M9)Pe_xUm1XV2+ujb@%i#J=NcT){1)TMw*|Sbum@4hnWu+kmT3m#nl+@TTz= zxjYE+bCH16j@yyB0q4Kd@Gssyu-fU!7Ut^!Thw~au)=L`nL8vNGJ2rg9N0hLxywBS3Cu^Wh49-GB=YYU(%w)NvX+md$ zP+f)xwi);{p`}oO*oRptM9EWX6^dIjLIW+*ruoSlN;x<}*h&-X7fzJDWdnU4Hup6I zu0PGfp6KJEndM0Cn_U>oHELaW)MRuyw$TTXLzTiH;|^(mXJch|DgfekybAV zQ^~Hn#>la&2hKQ^o$blFTv{;*3ei7#3Q@u3dk%X)5sUIH^>{$DNlv!^U~SR*D8U(l z@D`bj+$IV^WP^agXxY;((F0*+C?tAxLvOs@KtH>{?qr(ct{oYh&5vGzPW~9?GaF-F5I@qU) zqzU{TBYRd`k=3Lmv}b&GJRFbJJI4Ud?SWv)m|y;9oH%ScX58;`q27+poWNVMY+)~b zk)es|qE@4K8r@}-Bv!u)U+oj_s|xc#9i>lK$#3d3Pag;rGB?wSzs^@1iDxP6OWgF5 zFW&Zv+GzPpe}Xw8_#)ZQDzKsPs{dye;;M03r0KHwF(qMndJg4+Cas%h{cgsvWMk4% zGC{L`hDFyJ+l<=pb1TcoXu2^mW%^8)CW=h=547vw6G1gX{q)2BPbU|)Yb|v;adUTJoEiz#qEi%QjZBOFf(IpF3S;zHT-k8UERBNpas*s zfJJ)RHSezJuoR@;g1vb%-?%3@dXwjqVe%2$U&_=VrHGy}?K;p~q|cYYIDz*WWPt4L zS*F1-qP&k`H!$^&s|$}ChHwuL?P_CY-|aXtqj-@TFX>$6*)Srh+kP4a;ZyHM=tnd?E@OGYKV$GcCuZC@KMSadvi8w7^Kjb=QtU@u`28!DrIGSZ=cou zTaB);X`80FmdsNZV$Xm5Y(YC_svy!D#1legYfpIuulDp;A91u1YQ-WI^eI1RHH5xw zu3yW(X58yMH$51A*-3}JthZSTiy?5GRbXAAku{ja>$6@=S>8B2Q5Zr`PO9~{L2ZTA zhe@A5`K*;@mx)7jbg`sDSDs50Bb^xup2FjU_3ki+_3P`c)AK1KT4a-|P7}eK8mYHw z;=(z{=D2Ju3q=)VDfHuGVpZOBdBcs1Vzn-<-!RBw%U<{U^O>0rb@zc`o z5h~D8T;&g@btLjP@E3Pe<9#Kkue{DN1Yh~ivui`{$Ddj!mwXKZVSU?sZzlQhS3GWM zN4xCg;LHYdQ@kgfiQ>H_Rx!jxFTx3)xr36en59~%1K#gUlJ4kl!xXlvWuXi+TM=C| z9R=gJ5(D}lTTxiw@aR5J?fQKkvuS)QzV3!Ul0YO$?i~2g`FPDRn|a!Rc>`}P4fBT& zipq2OK7&`q(W)56xx$z4qYv1soLDgMN=CEzRUu)}i2LiT>!Omc?|kJDL`wSDUy<2Z z+5K@WM440>(%xULH_Ta=whrYZ=zq?T-(0QJbTVkbmizh^ueh4W zpd;mW?x}XJ;U13lANjl5bt84vR$3_fYFuyR3=3);w5?-#exSK){#CoLOID^fhRw3+ zEz+@QUX*WumS*hpq@}e6j!|N%*+Q6#>x~W($%z&5lN8Zg*TS>L@S7ZzwAWg~ze`5s zMb7Bqsqkx}rKB8Ug`YYU_t0b#Lok`@^+eSrX0EV;`6yeO&)yM-hPJZUmlC<go9o2B{& zxa2@81a$-k24CUj{{lGS{6Yi6yMab?OIDqIe_1}+wG1?zXWn?Y^GD~HN^`RIu9#Tf z9DJ6oTF+4Irk=i6^vZ4!8B&@X+$vd&Ohh<84qM|{yXCFmFbYJU(L;Npd~QQar8%z! zp|NUD9x2!Vka)An(O{+-#Cm_j2#b|<$~J?uYgJCI8rboEo~7O`8$NHjuD4q5s*|pc z9zY4kuV7qru33tP6gY~cBb|Kj($=JFb?@jLp1=;Q`-CzJpTqg+ZbUoVuG{Mm@fr!cnhmBkboM@VfoqEc?Ukoy7%D`~h`c7L4%&cUve8kNj46LNIv}PgBfK z!ie?xz8%_GWfeX;cqc>7m8(-ltq)Pxiyp>|-mb;8CT$NvT0zt9HiXiy_u;NUe^=|K zUzpf-X_}?$cfrugVr^%Z>nQQN0qnhv-vnt0%8rkCXg0mGdtHu&H{o<7PCuFL3_-2b z#TssbMtUjxT53CA=_XoumdjaV*|ZC%yYquFZd%H^fAKkH z!Nh-{AH=MpL*pV;#Me&#hLpAf`!f>NP<>S_>u1#`lwBd1djx2lMd^fAjwD>*C%=&U z_FLai*}VCx#0sBR2G{T}Vm6qhpgFST3zM8CP<)~o3c62Z%fZy|DXF&iRIgmG2m z{n41>+{<6`Bamj`{BW1sAji1xhrrObS6Zy;H-p$pW_7kVxG_YKh7P!nn#hHkFm1{h z+W7Bd1l&$f#qB@cDed}P2pd1YDRDyj=^mQWk>6d(|v-v(ORj>P4%}dtWp`}{;gNZ($0UtE>rUeSved`Hdv&}m=tHxqaSO*RRiXW~y*8K9E`>D@&;igX>3Ne4Pt4H@$rxfhuB6JZ7#~ zY=5H93aa`Iwe19sW~qEUH{?O!Qov|pX|PHANj!hmoBcD)+|j~26um{{&0)W)h1oSi z@lo=HnmyqUmg%$$6Brg#{vcytM5p~;1o{s?u>L3K7nPvG3~s-0>^mK}@Ad~ZyCpTr zh84pX9)2(TXC;dlAL45m!XBnAnLvw>`Frl|bwFMsq{~JFRd#}7YE!g-+)+sBd_nmL zBg)UJZOms)TI=BP`x*Dnjdj$igr04Wn># z&veQJ45Zn^Sf8mQ)uu+8UQEdFU}7jEl{Bd9Yo=#h`Z^!m6Sj>cLV@n8^M&yQ%{;>h z86uOO@h`5W=QGRv+_tP&>G|b*WF1usyjJF;dW(*I%R>T6J@`X!_&iWt#AvS3iiaN~ z$F_%7+o9=Hmd0pPxEO6DtKLA1eq4aCJ zeq{p}bT_|d!6^qM^NG7Ensy}$v?_{32l}=%Y3EO7h{;9ai3P2keu;pVt~XBplQt+= z^zLO+eH4@A>S#y-`nkU@Ah(!h-8Uo0pHZ<88!Yn-zW;gYNXDYBR62=Lu5U~++x?~z z?LDDOro@sYi6B|NW&I_C12FcJ`TH~GVC7?ZH7ssf zxYacHn`nl^;ncZ*Ne;?Nj&cWUOtf1$HG{nv-x*QeG`w9X#p%9M-NWvHmWYRbs^t!w zn+Q7k;;qyV0UC5f=(cMw;`()ly%OW8cZAwwu~lac&Zj_HhRT6zoM0B^<%NQMww;&0 ziRXm7d}w9T6Eist7yX$_oGjJ)Q;2Z1?BJ3!HU=#t1f@K#fro>juo`}Qg@-E?TCCs4 zvPQ+$C?{h22u_09{a?NW&l9i(WNO%X<}?BQkAwS9R%5VxE(ir2j!sjvvx`sW(O%cj7kiX}xj}iTb~VTDC z2CCSBIDFAGXgm|aLNbuG0L)O0{bE)GRFz(*k=Asa0o|zt_w9?5`buN|9BlP+SzzQd z#jG1&nNAsTKD6c@y0Mml(WxzQf7r=76&ob#7i}6xLJLTHUzHM&wbI>ftT@<;w()Z| zDC}17)Ue8Pxphu+i~F-l6rDm&RaI}lE5NGCPhwHmXYZ~t*XHcDv~YI;x3TKW&Ugzq z{rM8@nL3;Y(YDHopng{x->FxQV>fh|QGN3u5X?fNDuMv*Nd_HU=%|vv;P3=V%s^zH z6qSz6UzWmGnCizD04(O;P5%9j=L}?G?wwTuv3rD9F_k~{Av8Ks&PO)wtj-Q!(l*x2 zm2K%Od``DX>A3axJfS}C%@GcoV}4@2vXQVTCg&O$*RB@IZ_RTkH?Urz+M|B12{9K8 zj2s&`oFTXeQym`XlwAZZOuc)60vcp^f}iEkf=}wO_lij;&Jgv8-bEwjoSVi^ca%Bv`wPXFk12znjTs4G3%zKM0zFVKU)-#t3kHnz29?@cgccV3@Hmh z&olDC#U)PaNS|KgnTfNX&k9 zIh1nd#`7$(o4EcwfFuyz_%U5|TDz@A+LC=RuBzNUARE5O*z50(+z^E+6SKmKswlUp zm4f8|VrmO`Na=Apr4!FnMWoDdjLy^Tr7J3l6O_X z|Jb{1| zyv(z>3@PZ5Lwyu9dH${wZ~S{NSD=cBs?sBB$iPI19#X%-HHRg!m0R_i!`dHw!9|rg z_AHq8yXv9tNUb^R!KMw=>POF4GWO%Vm(?W-&{+;+tD&isVU(S2k}>S)1)QePZq(b? zMV4>8vg31_=Ih9xaFnhsi>LeiKs!`P+kCOVS6N4oA?5~z`Uh~|nnhI)N22)pb@$%t zeuBm|Z&Yf|#7M`O%H*Lw9|d#IV-K2kJZ_xmEIm;@hje=Q5%TZL=kVYJd!vvM+!ww- zw%j@bqI=zcV~yob=yT2`Y>GBQg!vDYgX>5~zfD*68n)&dAy1}^HR%gFM@)1?(PY%V z6&pNYb9dR#8hZ$#1MkKSpV39fSLLVysC3^!_MVZK-Y&JAk0Z0Bepz)x-3@?eHu`9` zhuY`bTF-8M>bhXRtgI3xFzr-bvORO3#4xK2SxqEi(mJ6GkJZDMGx3)2A(7GnOGN9w zJ}Ea6tFAgvn|cYpL>Qsn09!;qWBkpup;LLE;8@*3SAiSB?QJbk)Ukvm+Bx3RlUmKX zudED9#zZJg&F)JAqsmBo{~q65b=WSlhgt(S3S1a`n@{kq$nTSZ-J#=<&%5%0EDP9h z-J;bfF(2b?avPWCmjakI3O%56@z8#t4B)*rO7oKv*bQ)+pmb}AC6et|wnO&)@Ibn@ zFPuZ6XbYlE*%jT8y9pV?;~(kxq`@hKYYFvn3Q9R>8+8`4vO_6SMgHj1 zq?A?eP!eLNntzAV;%lVLlQ*fc-ksuh7$(J@`;ZHoz4_Qiv_Az(saBo--Ms1AuXM(K73K3LDj3Y5X zNi@x}m86}y+%Tl8i)3W{s87T%#YD8u{*rj3VVm6Zy5Sdpc*}VIIZR+IK!xfm|8_gm z0}S0_&{vxC%7`8=L;IcZ@)|arp=eCp7=AIW#{erbw3Y3@KhLnv) zkpsIfW}bD2e>&Owm1lC8!W4{Gl<19(c)}RPFx=k0bZ5N2&$oIyV>~l68}#AG_ppg| zK04ru1CVSrYKG($344R-eBT8}b*rV-JsOq-jB4SaG1`C_L^duFvkf)ZA zp=OdUUte=nW3TCk0#LI}y6T&T@CCdl85g>+Cs;V^-@}H&V=|eIW^TDVhOguC;fhL2 zKSZ6wBuNGYc5llH)B%300LU+ulsI2}qOf<~N!P03Y%L&aZ3S4zlly>!hfn4wSIOXK zn}E!cvzD#>O^2DUdQ4-{-Hk$_f2qE4MxUer2kZy8cEWdh*iNS^h5)bhleTz+qs@gJ zObyh@Uiiw?xPz(YEa%eE{7qNkJJSbRXdltmD=if+{3F#l&c9;uO?~ zf4=2VzDhN}?`Y~K9u9M@t-!tAl44?ppZRd}5FK~xdb}<8;bY)p-O%TC67^JJ*1^14L2m#jN~P#hpyTe< z>%Fd8@Il#VO&^LdO-dRMb76#fr)ij;=S7V9E=blQ zZRg~Sd1i%+jgeMVLm%7XB1x@SV*hM-{|l{TL@{vdJuVJ7=DZ>;^;4J`Uz^;xdh~v! zm9w2y@;ET1%d%H3tasNbj`~AY3woVyzs6FG56B9GtaqK|#9*%$pV9}Poy!5Ah)Ow70VBKKIaN{$s4v#?@+O8+wG1JeDXmi0y*S`7`2 zN#PS|k+aSePRrejYOLCVqPCdgmW?+}CD@aXmls2r)!y*waUrKwaGe)E*?2^L2Cb&q ziS=t{qDwi>F(sPJazA$_^Zjdf?{S&@4k$Q?wb^OXLR&+_0lf_iC!KY2x}&mDk!R7W zSI@T#rQu>laQEe~#1X>uHh_aCA>a-(V+H1Kn^~HQ&L|X-%Xy^~x#(uY%gb9P`-h}Z za(SGqbEn2|=2rT4qF?m`q?N_G&13`}9UT!++jRc+JEf&xZgbeU!htlq63`W^e<_ur zfHEr`UCIF?j{$FFfB9P4sAIZH^0B|M9jm`+c-yrnPu=5O!upT2h>U^0JuUA%O#zK% z1K2uFveQ6+f04~pu6RNw%HK*Zwvm09&^{Kpr_v=nD4Ofy)hhvfJ&V}eUtG&Yst}1XlTTc z_x#Kz>j{U#38`h1ELV4TU4ksBTFF}~X6T8z{DE@6*NXs&^5;`K`C|({|J_{)K=|(R z_zJ-F-Y$6~O%CkkEzz!*d7@;Cj_YMqYX6$@ylF04fsaG(d0Or%6Jpg44u|xdsvYq= zbHClD7Cjz+-i+o)I)($-mFogndk>L-Z=L+m@04-y?GjazYrmd8`7pu*`9Fu$?hUO= Y5vfXgvE&0nOHzQ2rlCgVV;kiE02o0hDgXcg literal 0 HcmV?d00001 diff --git a/media/rez_logo.svg b/media/rez_logo.svg new file mode 100644 index 0000000000..aaee2bddc4 --- /dev/null +++ b/media/rez_logo.svg @@ -0,0 +1,82 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/media/rez_logo.xcf b/media/rez_logo.xcf new file mode 100644 index 0000000000000000000000000000000000000000..71cfd5ea05a98f89c8f79139f4cb99b90c17831e GIT binary patch literal 335130 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 literal 0 HcmV?d00001 diff --git a/media/rez_logo_32.png b/media/rez_logo_32.png new file mode 100644 index 0000000000000000000000000000000000000000..067b87a0a47139e6736d3cb7ba359e046f40e1c8 GIT binary patch literal 1830 zcmV+>2if?EP)- zK~z}7t(RMD9A_2Bf8RH=ckg-~+xU_Q$JoS%TuMXJHl=AQD%yPrLMj&t3PdU*KFj zuIwmyPPu~Ef@l;o3}1{3yaasDf@&CqxSQMaFbMn^7*asC%j8Qx z$JY$7)h`YqQ4|ogc&_FCH*!|TO zVrk#8LV}b6X@E2|sdj_SJ7XATNNPgxdVY!X$BXr=DsaexqjjA|7(fm<;VbV7lBioq z=o*T!Wmk+^S+j86<@~V{ADpvUnsRV!nk#SGj2tg<;dq&3o1}9ff{=g%eftv3zUwf5 z!$pD#q;qik1Qfx)Xm)!H_#=Mo*}5lA?x$IzT?Qx(LU8-rv&>Dn-1C(bpL;qDHF*C| zB~Bh*;P1a(1OeL*q`=WoRXk?3GI_9y=YjAmXd5uiN}@Jo=Ffrs0IfX+k35qh+9si> zpsb;!C`@@I(~_1>1C})u6~kXnvUx`o+k^45HeOlzq0o@(kZgM-f#=JYz~}RU@iJU; zdAK(yH#D&KyBV?rCe-wuR~M!|s-7mEmSlSZuK?6Et-U51aBYoK(G7{c_5OH0M*(&O z#N`7BS^pHURRcZ4QHGvKKs6*t3T8%Y2q4xX$n=;iRUn|U=-U`gNuL|)KMBZm8DRj~ zx^5ZDfdSwP1blk`Q!RdJg^z@p(Has+wn?JxhF@F)yb4@>t41;=*m6%4kqDJ2ARE%1 zJGEklE{E}{l~hvDzc&tU695CIM;%NF?OP+DHJ+>S960&x0$OSMA4st4KyoE)brMhw zU+o~u4FCp#qyqXMjIXv{05zB&cQB)Z)^3BzH*Bt5w3#?(>reMo`F&tYnJ?e0K(xtknNLTf0 z;DPtX->lYI?-np>1^<*Q_W5O@0W-cvi!fmJvIE-Z2jYVM2V?Z@iQ#&hQ@>dRQ>>|c z?ux_oxLa3t0vHQ9wjL$laRSip8fF-R*-JG5KCvwVo(2Q%`+5uMO_I6)I=uZ-5n@ds zqJlF=mg|ZNz%dJ!n*q24+ze1&P^;qa-ts6F{QItL5x-Vl=-3(M)B6%g0cT$*Ql9Zv zI*ci}e6qsC1-q{36$@Tz5)D8l$V?E{;fc>V?aEX=%jy5R{>Ip_wyCV+B5aqp9TJNkf!-8Mo&up&pVC3Zzrw%Vrm1 zhl7_h4v=j(_{?JodbdYNZZa?f!qJ|_scOmv#kGqyMo(9$loZnE`jfzqEV#Pi&8P{) zH-H}k59@%fq5{%g29dY`AyBTSR@N-fd#E6$0$vAR0A949vYzq}-3vnN*nWI4PXGx` z!yr`uyb7EGUIj)hxbd-;(> sys.stderr, "Must be run from master" + sys.exit(1) + + # get current tag + latest_tag = run_proc("git", "describe", "--tags", "--abbrev=0") + + # see if latest release already matches tag + response = requests.get(get_url("releases/latest")) + response.raise_for_status() + latest_release_tag = response.json()["tag_name"] + + if latest_release_tag == latest_tag: + print >> sys.stderr, "Release for %s already exists" % latest_tag + sys.exit(1) + + # parse latest release out of changelog + changelog = parse_topmost_changelog() + + tag_name = changelog["version"] + if tag_name != latest_tag: + print >> sys.stderr, ( + "Latest entry in changelog (%s) doesn't match latest tag (%s)" + % (tag_name, latest_tag) + ) + sys.exit(1) + + data = dict( + tag_name=tag_name, + name=changelog["name"], + body=changelog["body"] + ) + + # create the release on github + response = requests.post( + get_url("releases"), + json=data, + headers=default_headers + ) + + response.raise_for_status() + url = "https://github.com/nerdvegas/rez/releases/tag/" + tag_name + print "Created release notes: " + url + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + + subparser = subparsers.add_parser("create-release-notes") + subparser.set_defaults(func=create_release_notes) + + subparser = subparsers.add_parser("create-changelog-entry") + subparser.set_defaults(func=create_changelog_entry) + + opts = parser.parse_args() + opts.func(opts) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..8eb77414b8 --- /dev/null +++ b/setup.py @@ -0,0 +1,116 @@ +from __future__ import with_statement +import fnmatch +import os +import os.path +import sys + + +try: + from setuptools import setup, find_packages +except ImportError: + print >> sys.stderr, "install failed - requires setuptools" + sys.exit(1) + + +if sys.version_info < (2, 6): + print >> sys.stderr, "install failed - requires python v2.6 or greater" + sys.exit(1) + + +def find_files(pattern, path=None, root="rez"): + paths = [] + basepath = os.path.realpath(os.path.join("src", root)) + path_ = basepath + if path: + path_ = os.path.join(path_, path) + + for root, _, files in os.walk(path_): + files = [x for x in files if fnmatch.fnmatch(x, pattern)] + files = [os.path.join(root, x) for x in files] + paths += [x[len(basepath):].lstrip(os.path.sep) for x in files] + + return paths + + +# get version from source +with open("src/rez/utils/_version.py") as f: + code = f.read().strip() +_rez_version = None # just to keep linting happy +exec(code) # inits _rez_version +version = _rez_version + + +scripts = [ + "rezolve", + "rez", + "rez-config", + "rez-build", + "rez-release", + "rez-env", + "rez-context", + "rez-suite", + "rez-interpret", + "rez-python", + "rez-selftest", + "rez-bind", + "rez-search", + "rez-view", + "rez-status", + "rez-help", + "rez-depends", + "rez-memcache", + "rez-yaml2py", + "bez", + "_rez_fwd", # TODO rename this _rez-forward for consistency + "_rez-complete", + "rez-gui" +] + + +setup( + name="rez", + version=version, + description=("A cross-platform packaging system that can build and " + "install multiple version of packages, and dynamically " + "configure resolved environments at runtime."), + keywords="package resolve version build install software management", + long_description=None, + url="https://github.com/nerdvegas/rez", + author="Allan Johns", + author_email="nerdvegas@gmail.com", + license="LGPL", + scripts=[os.path.join('bin', x) for x in scripts], + include_package_data=True, + zip_safe=False, + package_dir = {'': 'src'}, + packages=find_packages('src', exclude=["build_utils", + "build_utils.*", + "tests"]), + package_data = { + 'rez': + ['rezconfig', 'utils/logging.conf'] + + ['README*'] + + find_files('*', 'completion') + + find_files('*', 'tests/data'), + 'rezplugins': + find_files('rezconfig', root='rezplugins') + + find_files('*.cmake', 'build_system', root='rezplugins') + + find_files('*', 'build_system/template_files', root='rezplugins'), + 'rezgui': + find_files('rezguiconfig', root='rezgui') + + find_files('*', 'icons', root='rezgui') + }, + classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Topic :: Software Development", + "Topic :: System :: Software Distribution" + ] +) + diff --git a/src/build_utils/__init__.py b/src/build_utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/build_utils/add_license.py b/src/build_utils/add_license.py new file mode 100644 index 0000000000..e48eae70c9 --- /dev/null +++ b/src/build_utils/add_license.py @@ -0,0 +1,115 @@ +""" +Adds LGPL and Copyright notice to the end of each .py file. You need to run +this script from the src/ subdirectory of the rez source. +""" +import os +import os.path +from datetime import datetime + + +notice = \ +""" +Copyright 2013-{year} {authors}. + +This library is free software: you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +""" + +year = datetime.now().year + + +skip_dirs = [ + "vendor", + "build_utils", + "backport" +] + + +def extract_copyright_authors(txt): + lines = txt.split('\n') + for line in lines: + if line.startswith("# Copyright"): + if str(year) in line: + part = line.split(str(year))[-1] + elif str(year - 1) in line: + part = line.split(str(year - 1))[-1] + else: + return [] + + parts = part.strip().rstrip('.').split(',') + authors = [] + + for part in parts: + auth = part.strip() + if auth != "Allan Johns": + authors.append(auth) + + return authors + + return [] + + +if __name__ == "__main__": + filepaths = [] + + notice_lines = notice.strip().split('\n') + notice_lines = [("# %s" % x).rstrip() for x in notice_lines] + copyright_line = notice_lines[0] + notice_lines = notice_lines[1:] + + # find py files + for root, dirs, names in os.walk('.'): + for name in names: + if name.endswith(".py"): + filepath = os.path.join(root, name) + print "found: %s" % filepath + filepaths.append(filepath) + + for dirname in skip_dirs: + if dirname in dirs: + dirs.remove(dirname) + + # append notice to each py file + for filepath in filepaths: + with open(filepath) as f: + txt = f.read() + + lines = txt.split('\n') + + while lines and not lines[-1].strip(): + lines.pop() + + while lines and lines[-1].startswith('#'): + lines.pop() + + while lines and not lines[-1].strip(): + lines.pop() + + lines.append('') + lines.append('') + + authors = extract_copyright_authors(txt) + authors = ["Allan Johns"] + authors + + copyright_line_ = copyright_line.format( + year=year, authors=", ".join(authors)) + + lines.append(copyright_line_) + lines.extend(notice_lines) + lines.append('') + + new_txt = '\n'.join(lines) + + print "Writing updated %s..." % filepath + with open(filepath, 'w') as f: + f.write(new_txt) diff --git a/src/build_utils/distlib/__init__.py b/src/build_utils/distlib/__init__.py new file mode 100644 index 0000000000..2c9d3cc344 --- /dev/null +++ b/src/build_utils/distlib/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2014 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import logging + +__version__ = '0.2.0.dev0' + +class DistlibException(Exception): + pass + +try: + from logging import NullHandler +except ImportError: # pragma: no cover + class NullHandler(logging.Handler): + def handle(self, record): pass + def emit(self, record): pass + def createLock(self): self.lock = None + +logger = logging.getLogger(__name__) +logger.addHandler(NullHandler()) diff --git a/src/build_utils/distlib/_backport/__init__.py b/src/build_utils/distlib/_backport/__init__.py new file mode 100644 index 0000000000..f7dbf4c9aa --- /dev/null +++ b/src/build_utils/distlib/_backport/__init__.py @@ -0,0 +1,6 @@ +"""Modules copied from Python 3 standard libraries, for internal use only. + +Individual classes and functions are found in d2._backport.misc. Intended +usage is to always import things missing from 3.1 from that module: the +built-in/stdlib objects will be used if found. +""" diff --git a/src/build_utils/distlib/_backport/misc.py b/src/build_utils/distlib/_backport/misc.py new file mode 100644 index 0000000000..cfb318d34f --- /dev/null +++ b/src/build_utils/distlib/_backport/misc.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Backports for individual classes and functions.""" + +import os +import sys + +__all__ = ['cache_from_source', 'callable', 'fsencode'] + + +try: + from imp import cache_from_source +except ImportError: + def cache_from_source(py_file, debug=__debug__): + ext = debug and 'c' or 'o' + return py_file + ext + + +try: + callable = callable +except NameError: + from collections import Callable + + def callable(obj): + return isinstance(obj, Callable) + + +try: + fsencode = os.fsencode +except AttributeError: + def fsencode(filename): + if isinstance(filename, bytes): + return filename + elif isinstance(filename, str): + return filename.encode(sys.getfilesystemencoding()) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) diff --git a/src/build_utils/distlib/_backport/shutil.py b/src/build_utils/distlib/_backport/shutil.py new file mode 100644 index 0000000000..49ca852601 --- /dev/null +++ b/src/build_utils/distlib/_backport/shutil.py @@ -0,0 +1,761 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Utility functions for copying and archiving files and directory trees. + +XXX The functions here don't copy the resource fork or other metadata on Mac. + +""" + +import os +import sys +import stat +from os.path import abspath +import fnmatch +import collections +import errno +from . import tarfile + +try: + import bz2 + _BZ2_SUPPORTED = True +except ImportError: + _BZ2_SUPPORTED = False + +try: + from pwd import getpwnam +except ImportError: + getpwnam = None + +try: + from grp import getgrnam +except ImportError: + getgrnam = None + +__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", + "copytree", "move", "rmtree", "Error", "SpecialFileError", + "ExecError", "make_archive", "get_archive_formats", + "register_archive_format", "unregister_archive_format", + "get_unpack_formats", "register_unpack_format", + "unregister_unpack_format", "unpack_archive", "ignore_patterns"] + +class Error(EnvironmentError): + pass + +class SpecialFileError(EnvironmentError): + """Raised when trying to do a kind of operation (e.g. copying) which is + not supported on a special file (e.g. a named pipe)""" + +class ExecError(EnvironmentError): + """Raised when a command could not be executed""" + +class ReadError(EnvironmentError): + """Raised when an archive cannot be read""" + +class RegistryError(Exception): + """Raised when a registery operation with the archiving + and unpacking registeries fails""" + + +try: + WindowsError +except NameError: + WindowsError = None + +def copyfileobj(fsrc, fdst, length=16*1024): + """copy data from file-like object fsrc to file-like object fdst""" + while 1: + buf = fsrc.read(length) + if not buf: + break + fdst.write(buf) + +def _samefile(src, dst): + # Macintosh, Unix. + if hasattr(os.path, 'samefile'): + try: + return os.path.samefile(src, dst) + except OSError: + return False + + # All other platforms: check for same pathname. + return (os.path.normcase(os.path.abspath(src)) == + os.path.normcase(os.path.abspath(dst))) + +def copyfile(src, dst): + """Copy data from src to dst""" + if _samefile(src, dst): + raise Error("`%s` and `%s` are the same file" % (src, dst)) + + for fn in [src, dst]: + try: + st = os.stat(fn) + except OSError: + # File most likely does not exist + pass + else: + # XXX What about other special files? (sockets, devices...) + if stat.S_ISFIFO(st.st_mode): + raise SpecialFileError("`%s` is a named pipe" % fn) + + with open(src, 'rb') as fsrc: + with open(dst, 'wb') as fdst: + copyfileobj(fsrc, fdst) + +def copymode(src, dst): + """Copy mode bits from src to dst""" + if hasattr(os, 'chmod'): + st = os.stat(src) + mode = stat.S_IMODE(st.st_mode) + os.chmod(dst, mode) + +def copystat(src, dst): + """Copy all stat info (mode bits, atime, mtime, flags) from src to dst""" + st = os.stat(src) + mode = stat.S_IMODE(st.st_mode) + if hasattr(os, 'utime'): + os.utime(dst, (st.st_atime, st.st_mtime)) + if hasattr(os, 'chmod'): + os.chmod(dst, mode) + if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): + try: + os.chflags(dst, st.st_flags) + except OSError as why: + if (not hasattr(errno, 'EOPNOTSUPP') or + why.errno != errno.EOPNOTSUPP): + raise + +def copy(src, dst): + """Copy data and mode bits ("cp src dst"). + + The destination may be a directory. + + """ + if os.path.isdir(dst): + dst = os.path.join(dst, os.path.basename(src)) + copyfile(src, dst) + copymode(src, dst) + +def copy2(src, dst): + """Copy data and all stat info ("cp -p src dst"). + + The destination may be a directory. + + """ + if os.path.isdir(dst): + dst = os.path.join(dst, os.path.basename(src)) + copyfile(src, dst) + copystat(src, dst) + +def ignore_patterns(*patterns): + """Function that can be used as copytree() ignore parameter. + + Patterns is a sequence of glob-style patterns + that are used to exclude files""" + def _ignore_patterns(path, names): + ignored_names = [] + for pattern in patterns: + ignored_names.extend(fnmatch.filter(names, pattern)) + return set(ignored_names) + return _ignore_patterns + +def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, + ignore_dangling_symlinks=False): + """Recursively copy a directory tree. + + The destination directory must not already exist. + If exception(s) occur, an Error is raised with a list of reasons. + + If the optional symlinks flag is true, symbolic links in the + source tree result in symbolic links in the destination tree; if + it is false, the contents of the files pointed to by symbolic + links are copied. If the file pointed by the symlink doesn't + exist, an exception will be added in the list of errors raised in + an Error exception at the end of the copy process. + + You can set the optional ignore_dangling_symlinks flag to true if you + want to silence this exception. Notice that this has no effect on + platforms that don't support os.symlink. + + The optional ignore argument is a callable. If given, it + is called with the `src` parameter, which is the directory + being visited by copytree(), and `names` which is the list of + `src` contents, as returned by os.listdir(): + + callable(src, names) -> ignored_names + + Since copytree() is called recursively, the callable will be + called once for each directory that is copied. It returns a + list of names relative to the `src` directory that should + not be copied. + + The optional copy_function argument is a callable that will be used + to copy each file. It will be called with the source path and the + destination path as arguments. By default, copy2() is used, but any + function that supports the same signature (like copy()) can be used. + + """ + names = os.listdir(src) + if ignore is not None: + ignored_names = ignore(src, names) + else: + ignored_names = set() + + os.makedirs(dst) + errors = [] + for name in names: + if name in ignored_names: + continue + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if os.path.islink(srcname): + linkto = os.readlink(srcname) + if symlinks: + os.symlink(linkto, dstname) + else: + # ignore dangling symlink if the flag is on + if not os.path.exists(linkto) and ignore_dangling_symlinks: + continue + # otherwise let the copy occurs. copy2 will raise an error + copy_function(srcname, dstname) + elif os.path.isdir(srcname): + copytree(srcname, dstname, symlinks, ignore, copy_function) + else: + # Will raise a SpecialFileError for unsupported file types + copy_function(srcname, dstname) + # catch the Error from the recursive copytree so that we can + # continue with other files + except Error as err: + errors.extend(err.args[0]) + except EnvironmentError as why: + errors.append((srcname, dstname, str(why))) + try: + copystat(src, dst) + except OSError as why: + if WindowsError is not None and isinstance(why, WindowsError): + # Copying file access times may fail on Windows + pass + else: + errors.extend((src, dst, str(why))) + if errors: + raise Error(errors) + +def rmtree(path, ignore_errors=False, onerror=None): + """Recursively delete a directory tree. + + If ignore_errors is set, errors are ignored; otherwise, if onerror + is set, it is called to handle the error with arguments (func, + path, exc_info) where func is os.listdir, os.remove, or os.rmdir; + path is the argument to that function that caused it to fail; and + exc_info is a tuple returned by sys.exc_info(). If ignore_errors + is false and onerror is None, an exception is raised. + + """ + if ignore_errors: + def onerror(*args): + pass + elif onerror is None: + def onerror(*args): + raise + try: + if os.path.islink(path): + # symlinks to directories are forbidden, see bug #1669 + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, path, sys.exc_info()) + # can't continue even if onerror hook returns + return + names = [] + try: + names = os.listdir(path) + except os.error: + onerror(os.listdir, path, sys.exc_info()) + for name in names: + fullname = os.path.join(path, name) + try: + mode = os.lstat(fullname).st_mode + except os.error: + mode = 0 + if stat.S_ISDIR(mode): + rmtree(fullname, ignore_errors, onerror) + else: + try: + os.remove(fullname) + except os.error: + onerror(os.remove, fullname, sys.exc_info()) + try: + os.rmdir(path) + except os.error: + onerror(os.rmdir, path, sys.exc_info()) + + +def _basename(path): + # A basename() variant which first strips the trailing slash, if present. + # Thus we always get the last component of the path, even for directories. + return os.path.basename(path.rstrip(os.path.sep)) + +def move(src, dst): + """Recursively move a file or directory to another location. This is + similar to the Unix "mv" command. + + If the destination is a directory or a symlink to a directory, the source + is moved inside the directory. The destination path must not already + exist. + + If the destination already exists but is not a directory, it may be + overwritten depending on os.rename() semantics. + + If the destination is on our current filesystem, then rename() is used. + Otherwise, src is copied to the destination and then removed. + A lot more could be done here... A look at a mv.c shows a lot of + the issues this implementation glosses over. + + """ + real_dst = dst + if os.path.isdir(dst): + if _samefile(src, dst): + # We might be on a case insensitive filesystem, + # perform the rename anyway. + os.rename(src, dst) + return + + real_dst = os.path.join(dst, _basename(src)) + if os.path.exists(real_dst): + raise Error("Destination path '%s' already exists" % real_dst) + try: + os.rename(src, real_dst) + except OSError: + if os.path.isdir(src): + if _destinsrc(src, dst): + raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst)) + copytree(src, real_dst, symlinks=True) + rmtree(src) + else: + copy2(src, real_dst) + os.unlink(src) + +def _destinsrc(src, dst): + src = abspath(src) + dst = abspath(dst) + if not src.endswith(os.path.sep): + src += os.path.sep + if not dst.endswith(os.path.sep): + dst += os.path.sep + return dst.startswith(src) + +def _get_gid(name): + """Returns a gid, given a group name.""" + if getgrnam is None or name is None: + return None + try: + result = getgrnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _get_uid(name): + """Returns an uid, given a user name.""" + if getpwnam is None or name is None: + return None + try: + result = getpwnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, + owner=None, group=None, logger=None): + """Create a (possibly compressed) tar file from all the files under + 'base_dir'. + + 'compress' must be "gzip" (the default), "bzip2", or None. + + 'owner' and 'group' can be used to define an owner and a group for the + archive that is being built. If not provided, the current owner and group + will be used. + + The output tar file will be named 'base_name' + ".tar", possibly plus + the appropriate compression extension (".gz", or ".bz2"). + + Returns the output filename. + """ + tar_compression = {'gzip': 'gz', None: ''} + compress_ext = {'gzip': '.gz'} + + if _BZ2_SUPPORTED: + tar_compression['bzip2'] = 'bz2' + compress_ext['bzip2'] = '.bz2' + + # flags for compression program, each element of list will be an argument + if compress is not None and compress not in compress_ext: + raise ValueError("bad value for 'compress', or compression format not " + "supported : {0}".format(compress)) + + archive_name = base_name + '.tar' + compress_ext.get(compress, '') + archive_dir = os.path.dirname(archive_name) + + if not os.path.exists(archive_dir): + if logger is not None: + logger.info("creating %s", archive_dir) + if not dry_run: + os.makedirs(archive_dir) + + # creating the tarball + if logger is not None: + logger.info('Creating tar archive') + + uid = _get_uid(owner) + gid = _get_gid(group) + + def _set_uid_gid(tarinfo): + if gid is not None: + tarinfo.gid = gid + tarinfo.gname = group + if uid is not None: + tarinfo.uid = uid + tarinfo.uname = owner + return tarinfo + + if not dry_run: + tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) + try: + tar.add(base_dir, filter=_set_uid_gid) + finally: + tar.close() + + return archive_name + +def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False): + # XXX see if we want to keep an external call here + if verbose: + zipoptions = "-r" + else: + zipoptions = "-rq" + from distutils.errors import DistutilsExecError + from distutils.spawn import spawn + try: + spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run) + except DistutilsExecError: + # XXX really should distinguish between "couldn't find + # external 'zip' command" and "zip failed". + raise ExecError("unable to create zip file '%s': " + "could neither import the 'zipfile' module nor " + "find a standalone zip utility") % zip_filename + +def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): + """Create a zip file from all the files under 'base_dir'. + + The output zip file will be named 'base_name' + ".zip". Uses either the + "zipfile" Python module (if available) or the InfoZIP "zip" utility + (if installed and found on the default search path). If neither tool is + available, raises ExecError. Returns the name of the output zip + file. + """ + zip_filename = base_name + ".zip" + archive_dir = os.path.dirname(base_name) + + if not os.path.exists(archive_dir): + if logger is not None: + logger.info("creating %s", archive_dir) + if not dry_run: + os.makedirs(archive_dir) + + # If zipfile module is not available, try spawning an external 'zip' + # command. + try: + import zipfile + except ImportError: + zipfile = None + + if zipfile is None: + _call_external_zip(base_dir, zip_filename, verbose, dry_run) + else: + if logger is not None: + logger.info("creating '%s' and adding '%s' to it", + zip_filename, base_dir) + + if not dry_run: + zip = zipfile.ZipFile(zip_filename, "w", + compression=zipfile.ZIP_DEFLATED) + + for dirpath, dirnames, filenames in os.walk(base_dir): + for name in filenames: + path = os.path.normpath(os.path.join(dirpath, name)) + if os.path.isfile(path): + zip.write(path, path) + if logger is not None: + logger.info("adding '%s'", path) + zip.close() + + return zip_filename + +_ARCHIVE_FORMATS = { + 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), + 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), + 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"), + 'zip': (_make_zipfile, [], "ZIP file"), + } + +if _BZ2_SUPPORTED: + _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')], + "bzip2'ed tar-file") + +def get_archive_formats(): + """Returns a list of supported formats for archiving and unarchiving. + + Each element of the returned sequence is a tuple (name, description) + """ + formats = [(name, registry[2]) for name, registry in + _ARCHIVE_FORMATS.items()] + formats.sort() + return formats + +def register_archive_format(name, function, extra_args=None, description=''): + """Registers an archive format. + + name is the name of the format. function is the callable that will be + used to create archives. If provided, extra_args is a sequence of + (name, value) tuples that will be passed as arguments to the callable. + description can be provided to describe the format, and will be returned + by the get_archive_formats() function. + """ + if extra_args is None: + extra_args = [] + if not isinstance(function, collections.Callable): + raise TypeError('The %s object is not callable' % function) + if not isinstance(extra_args, (tuple, list)): + raise TypeError('extra_args needs to be a sequence') + for element in extra_args: + if not isinstance(element, (tuple, list)) or len(element) !=2: + raise TypeError('extra_args elements are : (arg_name, value)') + + _ARCHIVE_FORMATS[name] = (function, extra_args, description) + +def unregister_archive_format(name): + del _ARCHIVE_FORMATS[name] + +def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, + dry_run=0, owner=None, group=None, logger=None): + """Create an archive file (eg. zip or tar). + + 'base_name' is the name of the file to create, minus any format-specific + extension; 'format' is the archive format: one of "zip", "tar", "bztar" + or "gztar". + + 'root_dir' is a directory that will be the root directory of the + archive; ie. we typically chdir into 'root_dir' before creating the + archive. 'base_dir' is the directory where we start archiving from; + ie. 'base_dir' will be the common prefix of all files and + directories in the archive. 'root_dir' and 'base_dir' both default + to the current directory. Returns the name of the archive file. + + 'owner' and 'group' are used when creating a tar archive. By default, + uses the current owner and group. + """ + save_cwd = os.getcwd() + try: + if root_dir is not None: + if logger is not None: + logger.debug("changing into '%s'", root_dir) + base_name = os.path.abspath(base_name) + if not dry_run: + os.chdir(root_dir) + + if base_dir is None: + base_dir = os.curdir + + kwargs = {'dry_run': dry_run, 'logger': logger} + + try: + format_info = _ARCHIVE_FORMATS[format] + except KeyError: + raise ValueError("unknown archive format '%s'" % format) + + func = format_info[0] + for arg, val in format_info[1]: + kwargs[arg] = val + + if format != 'zip': + kwargs['owner'] = owner + kwargs['group'] = group + + filename = func(base_name, base_dir, **kwargs) + finally: + if root_dir is not None: + if logger is not None: + logger.debug("changing back to '%s'", save_cwd) + os.chdir(save_cwd) + + return filename + + +def get_unpack_formats(): + """Returns a list of supported formats for unpacking. + + Each element of the returned sequence is a tuple + (name, extensions, description) + """ + formats = [(name, info[0], info[3]) for name, info in + _UNPACK_FORMATS.items()] + formats.sort() + return formats + +def _check_unpack_options(extensions, function, extra_args): + """Checks what gets registered as an unpacker.""" + # first make sure no other unpacker is registered for this extension + existing_extensions = {} + for name, info in _UNPACK_FORMATS.items(): + for ext in info[0]: + existing_extensions[ext] = name + + for extension in extensions: + if extension in existing_extensions: + msg = '%s is already registered for "%s"' + raise RegistryError(msg % (extension, + existing_extensions[extension])) + + if not isinstance(function, collections.Callable): + raise TypeError('The registered function must be a callable') + + +def register_unpack_format(name, extensions, function, extra_args=None, + description=''): + """Registers an unpack format. + + `name` is the name of the format. `extensions` is a list of extensions + corresponding to the format. + + `function` is the callable that will be + used to unpack archives. The callable will receive archives to unpack. + If it's unable to handle an archive, it needs to raise a ReadError + exception. + + If provided, `extra_args` is a sequence of + (name, value) tuples that will be passed as arguments to the callable. + description can be provided to describe the format, and will be returned + by the get_unpack_formats() function. + """ + if extra_args is None: + extra_args = [] + _check_unpack_options(extensions, function, extra_args) + _UNPACK_FORMATS[name] = extensions, function, extra_args, description + +def unregister_unpack_format(name): + """Removes the pack format from the registery.""" + del _UNPACK_FORMATS[name] + +def _ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + +def _unpack_zipfile(filename, extract_dir): + """Unpack zip `filename` to `extract_dir` + """ + try: + import zipfile + except ImportError: + raise ReadError('zlib not supported, cannot unpack this archive.') + + if not zipfile.is_zipfile(filename): + raise ReadError("%s is not a zip file" % filename) + + zip = zipfile.ZipFile(filename) + try: + for info in zip.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name: + continue + + target = os.path.join(extract_dir, *name.split('/')) + if not target: + continue + + _ensure_directory(target) + if not name.endswith('/'): + # file + data = zip.read(info.filename) + f = open(target, 'wb') + try: + f.write(data) + finally: + f.close() + del data + finally: + zip.close() + +def _unpack_tarfile(filename, extract_dir): + """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` + """ + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise ReadError( + "%s is not a compressed or uncompressed tar file" % filename) + try: + tarobj.extractall(extract_dir) + finally: + tarobj.close() + +_UNPACK_FORMATS = { + 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"), + 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"), + 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file") + } + +if _BZ2_SUPPORTED: + _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [], + "bzip2'ed tar-file") + +def _find_unpack_format(filename): + for name, info in _UNPACK_FORMATS.items(): + for extension in info[0]: + if filename.endswith(extension): + return name + return None + +def unpack_archive(filename, extract_dir=None, format=None): + """Unpack an archive. + + `filename` is the name of the archive. + + `extract_dir` is the name of the target directory, where the archive + is unpacked. If not provided, the current working directory is used. + + `format` is the archive format: one of "zip", "tar", or "gztar". Or any + other registered format. If not provided, unpack_archive will use the + filename extension and see if an unpacker was registered for that + extension. + + In case none is found, a ValueError is raised. + """ + if extract_dir is None: + extract_dir = os.getcwd() + + if format is not None: + try: + format_info = _UNPACK_FORMATS[format] + except KeyError: + raise ValueError("Unknown unpack format '{0}'".format(format)) + + func = format_info[1] + func(filename, extract_dir, **dict(format_info[2])) + else: + # we need to look at the registered unpackers supported extensions + format = _find_unpack_format(filename) + if format is None: + raise ReadError("Unknown archive format '{0}'".format(filename)) + + func = _UNPACK_FORMATS[format][1] + kwargs = dict(_UNPACK_FORMATS[format][2]) + func(filename, extract_dir, **kwargs) diff --git a/src/build_utils/distlib/_backport/sysconfig.cfg b/src/build_utils/distlib/_backport/sysconfig.cfg new file mode 100644 index 0000000000..1746bd01c1 --- /dev/null +++ b/src/build_utils/distlib/_backport/sysconfig.cfg @@ -0,0 +1,84 @@ +[posix_prefix] +# Configuration directories. Some of these come straight out of the +# configure script. They are for implementing the other variables, not to +# be used directly in [resource_locations]. +confdir = /etc +datadir = /usr/share +libdir = /usr/lib +statedir = /var +# User resource directory +local = ~/.local/{distribution.name} + +stdlib = {base}/lib/python{py_version_short} +platstdlib = {platbase}/lib/python{py_version_short} +purelib = {base}/lib/python{py_version_short}/site-packages +platlib = {platbase}/lib/python{py_version_short}/site-packages +include = {base}/include/python{py_version_short}{abiflags} +platinclude = {platbase}/include/python{py_version_short}{abiflags} +data = {base} + +[posix_home] +stdlib = {base}/lib/python +platstdlib = {base}/lib/python +purelib = {base}/lib/python +platlib = {base}/lib/python +include = {base}/include/python +platinclude = {base}/include/python +scripts = {base}/bin +data = {base} + +[nt] +stdlib = {base}/Lib +platstdlib = {base}/Lib +purelib = {base}/Lib/site-packages +platlib = {base}/Lib/site-packages +include = {base}/Include +platinclude = {base}/Include +scripts = {base}/Scripts +data = {base} + +[os2] +stdlib = {base}/Lib +platstdlib = {base}/Lib +purelib = {base}/Lib/site-packages +platlib = {base}/Lib/site-packages +include = {base}/Include +platinclude = {base}/Include +scripts = {base}/Scripts +data = {base} + +[os2_home] +stdlib = {userbase}/lib/python{py_version_short} +platstdlib = {userbase}/lib/python{py_version_short} +purelib = {userbase}/lib/python{py_version_short}/site-packages +platlib = {userbase}/lib/python{py_version_short}/site-packages +include = {userbase}/include/python{py_version_short} +scripts = {userbase}/bin +data = {userbase} + +[nt_user] +stdlib = {userbase}/Python{py_version_nodot} +platstdlib = {userbase}/Python{py_version_nodot} +purelib = {userbase}/Python{py_version_nodot}/site-packages +platlib = {userbase}/Python{py_version_nodot}/site-packages +include = {userbase}/Python{py_version_nodot}/Include +scripts = {userbase}/Scripts +data = {userbase} + +[posix_user] +stdlib = {userbase}/lib/python{py_version_short} +platstdlib = {userbase}/lib/python{py_version_short} +purelib = {userbase}/lib/python{py_version_short}/site-packages +platlib = {userbase}/lib/python{py_version_short}/site-packages +include = {userbase}/include/python{py_version_short} +scripts = {userbase}/bin +data = {userbase} + +[osx_framework_user] +stdlib = {userbase}/lib/python +platstdlib = {userbase}/lib/python +purelib = {userbase}/lib/python/site-packages +platlib = {userbase}/lib/python/site-packages +include = {userbase}/include +scripts = {userbase}/bin +data = {userbase} diff --git a/src/build_utils/distlib/_backport/sysconfig.py b/src/build_utils/distlib/_backport/sysconfig.py new file mode 100644 index 0000000000..1d3132679f --- /dev/null +++ b/src/build_utils/distlib/_backport/sysconfig.py @@ -0,0 +1,788 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Access to Python's configuration information.""" + +import codecs +import os +import re +import sys +from os.path import pardir, realpath +try: + import configparser +except ImportError: + import ConfigParser as configparser + + +__all__ = [ + 'get_config_h_filename', + 'get_config_var', + 'get_config_vars', + 'get_makefile_filename', + 'get_path', + 'get_path_names', + 'get_paths', + 'get_platform', + 'get_python_version', + 'get_scheme_names', + 'parse_config_h', +] + + +def _safe_realpath(path): + try: + return realpath(path) + except OSError: + return path + + +if sys.executable: + _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable)) +else: + # sys.executable can be empty if argv[0] has been changed and Python is + # unable to retrieve the real program name + _PROJECT_BASE = _safe_realpath(os.getcwd()) + +if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower(): + _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir)) +# PC/VS7.1 +if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower(): + _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir)) +# PC/AMD64 +if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower(): + _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir)) + + +def is_python_build(): + for fn in ("Setup.dist", "Setup.local"): + if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)): + return True + return False + +_PYTHON_BUILD = is_python_build() + +_cfg_read = False + +def _ensure_cfg_read(): + global _cfg_read + if not _cfg_read: + from ..resources import finder + backport_package = __name__.rsplit('.', 1)[0] + _finder = finder(backport_package) + _cfgfile = _finder.find('sysconfig.cfg') + assert _cfgfile, 'sysconfig.cfg exists' + with _cfgfile.as_stream() as s: + _SCHEMES.readfp(s) + if _PYTHON_BUILD: + for scheme in ('posix_prefix', 'posix_home'): + _SCHEMES.set(scheme, 'include', '{srcdir}/Include') + _SCHEMES.set(scheme, 'platinclude', '{projectbase}/.') + + _cfg_read = True + + +_SCHEMES = configparser.RawConfigParser() +_VAR_REPL = re.compile(r'\{([^{]*?)\}') + +def _expand_globals(config): + _ensure_cfg_read() + if config.has_section('globals'): + globals = config.items('globals') + else: + globals = tuple() + + sections = config.sections() + for section in sections: + if section == 'globals': + continue + for option, value in globals: + if config.has_option(section, option): + continue + config.set(section, option, value) + config.remove_section('globals') + + # now expanding local variables defined in the cfg file + # + for section in config.sections(): + variables = dict(config.items(section)) + + def _replacer(matchobj): + name = matchobj.group(1) + if name in variables: + return variables[name] + return matchobj.group(0) + + for option, value in config.items(section): + config.set(section, option, _VAR_REPL.sub(_replacer, value)) + +#_expand_globals(_SCHEMES) + + # FIXME don't rely on sys.version here, its format is an implementation detail + # of CPython, use sys.version_info or sys.hexversion +_PY_VERSION = sys.version.split()[0] +_PY_VERSION_SHORT = sys.version[:3] +_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2] +_PREFIX = os.path.normpath(sys.prefix) +_EXEC_PREFIX = os.path.normpath(sys.exec_prefix) +_CONFIG_VARS = None +_USER_BASE = None + + +def _subst_vars(path, local_vars): + """In the string `path`, replace tokens like {some.thing} with the + corresponding value from the map `local_vars`. + + If there is no corresponding value, leave the token unchanged. + """ + def _replacer(matchobj): + name = matchobj.group(1) + if name in local_vars: + return local_vars[name] + elif name in os.environ: + return os.environ[name] + return matchobj.group(0) + return _VAR_REPL.sub(_replacer, path) + + +def _extend_dict(target_dict, other_dict): + target_keys = target_dict.keys() + for key, value in other_dict.items(): + if key in target_keys: + continue + target_dict[key] = value + + +def _expand_vars(scheme, vars): + res = {} + if vars is None: + vars = {} + _extend_dict(vars, get_config_vars()) + + for key, value in _SCHEMES.items(scheme): + if os.name in ('posix', 'nt'): + value = os.path.expanduser(value) + res[key] = os.path.normpath(_subst_vars(value, vars)) + return res + + +def format_value(value, vars): + def _replacer(matchobj): + name = matchobj.group(1) + if name in vars: + return vars[name] + return matchobj.group(0) + return _VAR_REPL.sub(_replacer, value) + + +def _get_default_scheme(): + if os.name == 'posix': + # the default scheme for posix is posix_prefix + return 'posix_prefix' + return os.name + + +def _getuserbase(): + env_base = os.environ.get("PYTHONUSERBASE", None) + + def joinuser(*args): + return os.path.expanduser(os.path.join(*args)) + + # what about 'os2emx', 'riscos' ? + if os.name == "nt": + base = os.environ.get("APPDATA") or "~" + if env_base: + return env_base + else: + return joinuser(base, "Python") + + if sys.platform == "darwin": + framework = get_config_var("PYTHONFRAMEWORK") + if framework: + if env_base: + return env_base + else: + return joinuser("~", "Library", framework, "%d.%d" % + sys.version_info[:2]) + + if env_base: + return env_base + else: + return joinuser("~", ".local") + + +def _parse_makefile(filename, vars=None): + """Parse a Makefile-style file. + + A dictionary containing name/value pairs is returned. If an + optional dictionary is passed in as the second argument, it is + used instead of a new dictionary. + """ + # Regexes needed for parsing Makefile (and similar syntaxes, + # like old-style Setup files). + _variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") + _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") + _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") + + if vars is None: + vars = {} + done = {} + notdone = {} + + with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f: + lines = f.readlines() + + for line in lines: + if line.startswith('#') or line.strip() == '': + continue + m = _variable_rx.match(line) + if m: + n, v = m.group(1, 2) + v = v.strip() + # `$$' is a literal `$' in make + tmpv = v.replace('$$', '') + + if "$" in tmpv: + notdone[n] = v + else: + try: + v = int(v) + except ValueError: + # insert literal `$' + done[n] = v.replace('$$', '$') + else: + done[n] = v + + # do variable interpolation here + variables = list(notdone.keys()) + + # Variables with a 'PY_' prefix in the makefile. These need to + # be made available without that prefix through sysconfig. + # Special care is needed to ensure that variable expansion works, even + # if the expansion uses the name without a prefix. + renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') + + while len(variables) > 0: + for name in tuple(variables): + value = notdone[name] + m = _findvar1_rx.search(value) or _findvar2_rx.search(value) + if m is not None: + n = m.group(1) + found = True + if n in done: + item = str(done[n]) + elif n in notdone: + # get it on a subsequent round + found = False + elif n in os.environ: + # do it like make: fall back to environment + item = os.environ[n] + + elif n in renamed_variables: + if (name.startswith('PY_') and + name[3:] in renamed_variables): + item = "" + + elif 'PY_' + n in notdone: + found = False + + else: + item = str(done['PY_' + n]) + + else: + done[n] = item = "" + + if found: + after = value[m.end():] + value = value[:m.start()] + item + after + if "$" in after: + notdone[name] = value + else: + try: + value = int(value) + except ValueError: + done[name] = value.strip() + else: + done[name] = value + variables.remove(name) + + if (name.startswith('PY_') and + name[3:] in renamed_variables): + + name = name[3:] + if name not in done: + done[name] = value + + else: + # bogus variable reference (e.g. "prefix=$/opt/python"); + # just drop it since we can't deal + done[name] = value + variables.remove(name) + + # strip spurious spaces + for k, v in done.items(): + if isinstance(v, str): + done[k] = v.strip() + + # save the results in the global dictionary + vars.update(done) + return vars + + +def get_makefile_filename(): + """Return the path of the Makefile.""" + if _PYTHON_BUILD: + return os.path.join(_PROJECT_BASE, "Makefile") + if hasattr(sys, 'abiflags'): + config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags) + else: + config_dir_name = 'config' + return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile') + + +def _init_posix(vars): + """Initialize the module as appropriate for POSIX systems.""" + # load the installed Makefile: + makefile = get_makefile_filename() + try: + _parse_makefile(makefile, vars) + except IOError as e: + msg = "invalid Python installation: unable to open %s" % makefile + if hasattr(e, "strerror"): + msg = msg + " (%s)" % e.strerror + raise IOError(msg) + # load the installed pyconfig.h: + config_h = get_config_h_filename() + try: + with open(config_h) as f: + parse_config_h(f, vars) + except IOError as e: + msg = "invalid Python installation: unable to open %s" % config_h + if hasattr(e, "strerror"): + msg = msg + " (%s)" % e.strerror + raise IOError(msg) + # On AIX, there are wrong paths to the linker scripts in the Makefile + # -- these paths are relative to the Python source, but when installed + # the scripts are in another directory. + if _PYTHON_BUILD: + vars['LDSHARED'] = vars['BLDSHARED'] + + +def _init_non_posix(vars): + """Initialize the module as appropriate for NT""" + # set basic install directories + vars['LIBDEST'] = get_path('stdlib') + vars['BINLIBDEST'] = get_path('platstdlib') + vars['INCLUDEPY'] = get_path('include') + vars['SO'] = '.pyd' + vars['EXE'] = '.exe' + vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT + vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable)) + +# +# public APIs +# + + +def parse_config_h(fp, vars=None): + """Parse a config.h-style file. + + A dictionary containing name/value pairs is returned. If an + optional dictionary is passed in as the second argument, it is + used instead of a new dictionary. + """ + if vars is None: + vars = {} + define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n") + undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n") + + while True: + line = fp.readline() + if not line: + break + m = define_rx.match(line) + if m: + n, v = m.group(1, 2) + try: + v = int(v) + except ValueError: + pass + vars[n] = v + else: + m = undef_rx.match(line) + if m: + vars[m.group(1)] = 0 + return vars + + +def get_config_h_filename(): + """Return the path of pyconfig.h.""" + if _PYTHON_BUILD: + if os.name == "nt": + inc_dir = os.path.join(_PROJECT_BASE, "PC") + else: + inc_dir = _PROJECT_BASE + else: + inc_dir = get_path('platinclude') + return os.path.join(inc_dir, 'pyconfig.h') + + +def get_scheme_names(): + """Return a tuple containing the schemes names.""" + return tuple(sorted(_SCHEMES.sections())) + + +def get_path_names(): + """Return a tuple containing the paths names.""" + # xxx see if we want a static list + return _SCHEMES.options('posix_prefix') + + +def get_paths(scheme=_get_default_scheme(), vars=None, expand=True): + """Return a mapping containing an install scheme. + + ``scheme`` is the install scheme name. If not provided, it will + return the default scheme for the current platform. + """ + _ensure_cfg_read() + if expand: + return _expand_vars(scheme, vars) + else: + return dict(_SCHEMES.items(scheme)) + + +def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True): + """Return a path corresponding to the scheme. + + ``scheme`` is the install scheme name. + """ + return get_paths(scheme, vars, expand)[name] + + +def get_config_vars(*args): + """With no arguments, return a dictionary of all configuration + variables relevant for the current platform. + + On Unix, this means every variable defined in Python's installed Makefile; + On Windows and Mac OS it's a much smaller set. + + With arguments, return a list of values that result from looking up + each argument in the configuration variable dictionary. + """ + global _CONFIG_VARS + if _CONFIG_VARS is None: + _CONFIG_VARS = {} + # Normalized versions of prefix and exec_prefix are handy to have; + # in fact, these are the standard versions used most places in the + # distutils2 module. + _CONFIG_VARS['prefix'] = _PREFIX + _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX + _CONFIG_VARS['py_version'] = _PY_VERSION + _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT + _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2] + _CONFIG_VARS['base'] = _PREFIX + _CONFIG_VARS['platbase'] = _EXEC_PREFIX + _CONFIG_VARS['projectbase'] = _PROJECT_BASE + try: + _CONFIG_VARS['abiflags'] = sys.abiflags + except AttributeError: + # sys.abiflags may not be defined on all platforms. + _CONFIG_VARS['abiflags'] = '' + + if os.name in ('nt', 'os2'): + _init_non_posix(_CONFIG_VARS) + if os.name == 'posix': + _init_posix(_CONFIG_VARS) + # Setting 'userbase' is done below the call to the + # init function to enable using 'get_config_var' in + # the init-function. + if sys.version >= '2.6': + _CONFIG_VARS['userbase'] = _getuserbase() + + if 'srcdir' not in _CONFIG_VARS: + _CONFIG_VARS['srcdir'] = _PROJECT_BASE + else: + _CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir']) + + # Convert srcdir into an absolute path if it appears necessary. + # Normally it is relative to the build directory. However, during + # testing, for example, we might be running a non-installed python + # from a different directory. + if _PYTHON_BUILD and os.name == "posix": + base = _PROJECT_BASE + try: + cwd = os.getcwd() + except OSError: + cwd = None + if (not os.path.isabs(_CONFIG_VARS['srcdir']) and + base != cwd): + # srcdir is relative and we are not in the same directory + # as the executable. Assume executable is in the build + # directory and make srcdir absolute. + srcdir = os.path.join(base, _CONFIG_VARS['srcdir']) + _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir) + + if sys.platform == 'darwin': + kernel_version = os.uname()[2] # Kernel version (8.4.3) + major_version = int(kernel_version.split('.')[0]) + + if major_version < 8: + # On Mac OS X before 10.4, check if -arch and -isysroot + # are in CFLAGS or LDFLAGS and remove them if they are. + # This is needed when building extensions on a 10.3 system + # using a universal build of python. + for key in ('LDFLAGS', 'BASECFLAGS', + # a number of derived variables. These need to be + # patched up as well. + 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): + flags = _CONFIG_VARS[key] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = re.sub('-isysroot [^ \t]*', ' ', flags) + _CONFIG_VARS[key] = flags + else: + # Allow the user to override the architecture flags using + # an environment variable. + # NOTE: This name was introduced by Apple in OSX 10.5 and + # is used by several scripting languages distributed with + # that OS release. + if 'ARCHFLAGS' in os.environ: + arch = os.environ['ARCHFLAGS'] + for key in ('LDFLAGS', 'BASECFLAGS', + # a number of derived variables. These need to be + # patched up as well. + 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): + + flags = _CONFIG_VARS[key] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = flags + ' ' + arch + _CONFIG_VARS[key] = flags + + # If we're on OSX 10.5 or later and the user tries to + # compiles an extension using an SDK that is not present + # on the current machine it is better to not use an SDK + # than to fail. + # + # The major usecase for this is users using a Python.org + # binary installer on OSX 10.6: that installer uses + # the 10.4u SDK, but that SDK is not installed by default + # when you install Xcode. + # + CFLAGS = _CONFIG_VARS.get('CFLAGS', '') + m = re.search('-isysroot\s+(\S+)', CFLAGS) + if m is not None: + sdk = m.group(1) + if not os.path.exists(sdk): + for key in ('LDFLAGS', 'BASECFLAGS', + # a number of derived variables. These need to be + # patched up as well. + 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): + + flags = _CONFIG_VARS[key] + flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags) + _CONFIG_VARS[key] = flags + + if args: + vals = [] + for name in args: + vals.append(_CONFIG_VARS.get(name)) + return vals + else: + return _CONFIG_VARS + + +def get_config_var(name): + """Return the value of a single variable using the dictionary returned by + 'get_config_vars()'. + + Equivalent to get_config_vars().get(name) + """ + return get_config_vars().get(name) + + +def 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; eg. 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 + + Windows will return one of: + win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win-ia64 (64bit Windows on Itanium) + win32 (all others - specifically, sys.platform is returned) + + For other non-POSIX platforms, currently just returns 'sys.platform'. + """ + if os.name == 'nt': + # sniff sys.version for architecture. + prefix = " bit (" + i = sys.version.find(prefix) + if i == -1: + return sys.platform + j = sys.version.find(")", i) + look = sys.version[i+len(prefix):j].lower() + if look == 'amd64': + return 'win-amd64' + if look == 'itanium': + return 'win-ia64' + return sys.platform + + if os.name != "posix" or not hasattr(os, 'uname'): + # XXX what about the architecture? NT is Intel or Alpha, + # Mac OS is M68k or PPC, etc. + return sys.platform + + # Try to distinguish various flavours of Unix + osname, host, release, version, machine = os.uname() + + # Convert the OS name to lowercase, remove '/' characters + # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh") + osname = osname.lower().replace('/', '') + machine = machine.replace(' ', '_') + machine = machine.replace('/', '-') + + if osname[:5] == "linux": + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return "%s-%s" % (osname, machine) + elif osname[:5] == "sunos": + if release[0] >= "5": # SunOS 5 == Solaris 2 + osname = "solaris" + release = "%d.%s" % (int(release[0]) - 3, release[2:]) + # fall through to standard osname-release-machine representation + elif osname[:4] == "irix": # could be "irix64"! + return "%s-%s" % (osname, release) + elif osname[:3] == "aix": + return "%s-%s.%s" % (osname, version, release) + elif osname[:6] == "cygwin": + osname = "cygwin" + rel_re = re.compile(r'[\d.]+') + m = rel_re.match(release) + if m: + release = m.group() + elif osname[:6] == "darwin": + # + # For our purposes, we'll assume that the system version from + # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set + # to. This makes the compatibility story a bit more sane because the + # machine is going to compile and link as if it were + # MACOSX_DEPLOYMENT_TARGET. + cfgvars = get_config_vars() + macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET') + + if True: + # Always calculate the release of the running machine, + # needed to determine if we can build fat binaries or not. + + macrelease = macver + # Get the system version. Reading this plist is a documented + # way to get the system version (see the documentation for + # the Gestalt Manager) + try: + f = open('/System/Library/CoreServices/SystemVersion.plist') + except IOError: + # We're on a plain darwin box, fall back to the default + # behaviour. + pass + else: + try: + m = re.search(r'ProductUserVisibleVersion\s*' + r'(.*?)', f.read()) + finally: + f.close() + if m is not None: + macrelease = '.'.join(m.group(1).split('.')[:2]) + # else: fall back to the default behaviour + + if not macver: + macver = macrelease + + if macver: + release = macver + osname = "macosx" + + if ((macrelease + '.') >= '10.4.' and + '-arch' in get_config_vars().get('CFLAGS', '').strip()): + # The universal build will build fat binaries, but not on + # systems before 10.4 + # + # Try to detect 4-way universal builds, those have machine-type + # 'universal' instead of 'fat'. + + machine = 'fat' + cflags = get_config_vars().get('CFLAGS') + + archs = re.findall('-arch\s+(\S+)', cflags) + archs = tuple(sorted(set(archs))) + + if len(archs) == 1: + machine = archs[0] + elif archs == ('i386', 'ppc'): + machine = 'fat' + elif archs == ('i386', 'x86_64'): + machine = 'intel' + elif archs == ('i386', 'ppc', 'x86_64'): + machine = 'fat3' + elif archs == ('ppc64', 'x86_64'): + machine = 'fat64' + elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): + machine = 'universal' + else: + raise ValueError( + "Don't know machine value for archs=%r" % (archs,)) + + elif machine == 'i386': + # On OSX the machine type returned by uname is always the + # 32-bit variant, even if the executable architecture is + # the 64-bit variant + if sys.maxsize >= 2**32: + machine = 'x86_64' + + elif machine in ('PowerPC', 'Power_Macintosh'): + # Pick a sane name for the PPC architecture. + # See 'i386' case + if sys.maxsize >= 2**32: + machine = 'ppc64' + else: + machine = 'ppc' + + return "%s-%s-%s" % (osname, release, machine) + + +def get_python_version(): + return _PY_VERSION_SHORT + + +def _print_dict(title, data): + for index, (key, value) in enumerate(sorted(data.items())): + if index == 0: + print('%s: ' % (title)) + print('\t%s = "%s"' % (key, value)) + + +def _main(): + """Display all information sysconfig detains.""" + print('Platform: "%s"' % get_platform()) + print('Python version: "%s"' % get_python_version()) + print('Current installation scheme: "%s"' % _get_default_scheme()) + print() + _print_dict('Paths', get_paths()) + print() + _print_dict('Variables', get_config_vars()) + + +if __name__ == '__main__': + _main() diff --git a/src/build_utils/distlib/_backport/tarfile.py b/src/build_utils/distlib/_backport/tarfile.py new file mode 100644 index 0000000000..0580fb7953 --- /dev/null +++ b/src/build_utils/distlib/_backport/tarfile.py @@ -0,0 +1,2607 @@ +#------------------------------------------------------------------- +# tarfile.py +#------------------------------------------------------------------- +# Copyright (C) 2002 Lars Gustaebel +# All rights reserved. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +from __future__ import print_function + +"""Read from and write to tar format archives. +""" + +__version__ = "$Revision$" + +version = "0.9.0" +__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)" +__date__ = "$Date: 2011-02-25 17:42:01 +0200 (Fri, 25 Feb 2011) $" +__cvsid__ = "$Id: tarfile.py 88586 2011-02-25 15:42:01Z marc-andre.lemburg $" +__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend." + +#--------- +# Imports +#--------- +import sys +import os +import stat +import errno +import time +import struct +import copy +import re + +try: + import grp, pwd +except ImportError: + grp = pwd = None + +# os.symlink on Windows prior to 6.0 raises NotImplementedError +symlink_exception = (AttributeError, NotImplementedError) +try: + # WindowsError (1314) will be raised if the caller does not hold the + # SeCreateSymbolicLinkPrivilege privilege + symlink_exception += (WindowsError,) +except NameError: + pass + +# from tarfile import * +__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"] + +if sys.version_info[0] < 3: + import __builtin__ as builtins +else: + import builtins + +_open = builtins.open # Since 'open' is TarFile.open + +#--------------------------------------------------------- +# tar constants +#--------------------------------------------------------- +NUL = b"\0" # the null character +BLOCKSIZE = 512 # length of processing blocks +RECORDSIZE = BLOCKSIZE * 20 # length of records +GNU_MAGIC = b"ustar \0" # magic gnu tar string +POSIX_MAGIC = b"ustar\x0000" # magic posix tar string + +LENGTH_NAME = 100 # maximum length of a filename +LENGTH_LINK = 100 # maximum length of a linkname +LENGTH_PREFIX = 155 # maximum length of the prefix field + +REGTYPE = b"0" # regular file +AREGTYPE = b"\0" # regular file +LNKTYPE = b"1" # link (inside tarfile) +SYMTYPE = b"2" # symbolic link +CHRTYPE = b"3" # character special device +BLKTYPE = b"4" # block special device +DIRTYPE = b"5" # directory +FIFOTYPE = b"6" # fifo special device +CONTTYPE = b"7" # contiguous file + +GNUTYPE_LONGNAME = b"L" # GNU tar longname +GNUTYPE_LONGLINK = b"K" # GNU tar longlink +GNUTYPE_SPARSE = b"S" # GNU tar sparse file + +XHDTYPE = b"x" # POSIX.1-2001 extended header +XGLTYPE = b"g" # POSIX.1-2001 global header +SOLARIS_XHDTYPE = b"X" # Solaris extended header + +USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format +GNU_FORMAT = 1 # GNU tar format +PAX_FORMAT = 2 # POSIX.1-2001 (pax) format +DEFAULT_FORMAT = GNU_FORMAT + +#--------------------------------------------------------- +# tarfile constants +#--------------------------------------------------------- +# File types that tarfile supports: +SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE, + SYMTYPE, DIRTYPE, FIFOTYPE, + CONTTYPE, CHRTYPE, BLKTYPE, + GNUTYPE_LONGNAME, GNUTYPE_LONGLINK, + GNUTYPE_SPARSE) + +# File types that will be treated as a regular file. +REGULAR_TYPES = (REGTYPE, AREGTYPE, + CONTTYPE, GNUTYPE_SPARSE) + +# File types that are part of the GNU tar format. +GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK, + GNUTYPE_SPARSE) + +# Fields from a pax header that override a TarInfo attribute. +PAX_FIELDS = ("path", "linkpath", "size", "mtime", + "uid", "gid", "uname", "gname") + +# Fields from a pax header that are affected by hdrcharset. +PAX_NAME_FIELDS = set(("path", "linkpath", "uname", "gname")) + +# Fields in a pax header that are numbers, all other fields +# are treated as strings. +PAX_NUMBER_FIELDS = { + "atime": float, + "ctime": float, + "mtime": float, + "uid": int, + "gid": int, + "size": int +} + +#--------------------------------------------------------- +# Bits used in the mode field, values in octal. +#--------------------------------------------------------- +S_IFLNK = 0o120000 # symbolic link +S_IFREG = 0o100000 # regular file +S_IFBLK = 0o060000 # block device +S_IFDIR = 0o040000 # directory +S_IFCHR = 0o020000 # character device +S_IFIFO = 0o010000 # fifo + +TSUID = 0o4000 # set UID on execution +TSGID = 0o2000 # set GID on execution +TSVTX = 0o1000 # reserved + +TUREAD = 0o400 # read by owner +TUWRITE = 0o200 # write by owner +TUEXEC = 0o100 # execute/search by owner +TGREAD = 0o040 # read by group +TGWRITE = 0o020 # write by group +TGEXEC = 0o010 # execute/search by group +TOREAD = 0o004 # read by other +TOWRITE = 0o002 # write by other +TOEXEC = 0o001 # execute/search by other + +#--------------------------------------------------------- +# initialization +#--------------------------------------------------------- +if os.name in ("nt", "ce"): + ENCODING = "utf-8" +else: + ENCODING = sys.getfilesystemencoding() + +#--------------------------------------------------------- +# Some useful functions +#--------------------------------------------------------- + +def stn(s, length, encoding, errors): + """Convert a string to a null-terminated bytes object. + """ + s = s.encode(encoding, errors) + return s[:length] + (length - len(s)) * NUL + +def nts(s, encoding, errors): + """Convert a null-terminated bytes object to a string. + """ + p = s.find(b"\0") + if p != -1: + s = s[:p] + return s.decode(encoding, errors) + +def nti(s): + """Convert a number field to a python number. + """ + # There are two possible encodings for a number field, see + # itn() below. + if s[0] != chr(0o200): + try: + n = int(nts(s, "ascii", "strict") or "0", 8) + except ValueError: + raise InvalidHeaderError("invalid header") + else: + n = 0 + for i in range(len(s) - 1): + n <<= 8 + n += ord(s[i + 1]) + return n + +def itn(n, digits=8, format=DEFAULT_FORMAT): + """Convert a python number to a number field. + """ + # POSIX 1003.1-1988 requires numbers to be encoded as a string of + # octal digits followed by a null-byte, this allows values up to + # (8**(digits-1))-1. GNU tar allows storing numbers greater than + # that if necessary. A leading 0o200 byte indicates this particular + # encoding, the following digits-1 bytes are a big-endian + # representation. This allows values up to (256**(digits-1))-1. + if 0 <= n < 8 ** (digits - 1): + s = ("%0*o" % (digits - 1, n)).encode("ascii") + NUL + else: + if format != GNU_FORMAT or n >= 256 ** (digits - 1): + raise ValueError("overflow in number field") + + if n < 0: + # XXX We mimic GNU tar's behaviour with negative numbers, + # this could raise OverflowError. + n = struct.unpack("L", struct.pack("l", n))[0] + + s = bytearray() + for i in range(digits - 1): + s.insert(0, n & 0o377) + n >>= 8 + s.insert(0, 0o200) + return s + +def calc_chksums(buf): + """Calculate the checksum for a member's header by summing up all + characters except for the chksum field which is treated as if + it was filled with spaces. According to the GNU tar sources, + some tars (Sun and NeXT) calculate chksum with signed char, + which will be different if there are chars in the buffer with + the high bit set. So we calculate two checksums, unsigned and + signed. + """ + unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512])) + signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512])) + return unsigned_chksum, signed_chksum + +def copyfileobj(src, dst, length=None): + """Copy length bytes from fileobj src to fileobj dst. + If length is None, copy the entire content. + """ + if length == 0: + return + if length is None: + while True: + buf = src.read(16*1024) + if not buf: + break + dst.write(buf) + return + + BUFSIZE = 16 * 1024 + blocks, remainder = divmod(length, BUFSIZE) + for b in range(blocks): + buf = src.read(BUFSIZE) + if len(buf) < BUFSIZE: + raise IOError("end of file reached") + dst.write(buf) + + if remainder != 0: + buf = src.read(remainder) + if len(buf) < remainder: + raise IOError("end of file reached") + dst.write(buf) + return + +filemode_table = ( + ((S_IFLNK, "l"), + (S_IFREG, "-"), + (S_IFBLK, "b"), + (S_IFDIR, "d"), + (S_IFCHR, "c"), + (S_IFIFO, "p")), + + ((TUREAD, "r"),), + ((TUWRITE, "w"),), + ((TUEXEC|TSUID, "s"), + (TSUID, "S"), + (TUEXEC, "x")), + + ((TGREAD, "r"),), + ((TGWRITE, "w"),), + ((TGEXEC|TSGID, "s"), + (TSGID, "S"), + (TGEXEC, "x")), + + ((TOREAD, "r"),), + ((TOWRITE, "w"),), + ((TOEXEC|TSVTX, "t"), + (TSVTX, "T"), + (TOEXEC, "x")) +) + +def filemode(mode): + """Convert a file's mode to a string of the form + -rwxrwxrwx. + Used by TarFile.list() + """ + perm = [] + for table in filemode_table: + for bit, char in table: + if mode & bit == bit: + perm.append(char) + break + else: + perm.append("-") + return "".join(perm) + +class TarError(Exception): + """Base exception.""" + pass +class ExtractError(TarError): + """General exception for extract errors.""" + pass +class ReadError(TarError): + """Exception for unreadble tar archives.""" + pass +class CompressionError(TarError): + """Exception for unavailable compression methods.""" + pass +class StreamError(TarError): + """Exception for unsupported operations on stream-like TarFiles.""" + pass +class HeaderError(TarError): + """Base exception for header errors.""" + pass +class EmptyHeaderError(HeaderError): + """Exception for empty headers.""" + pass +class TruncatedHeaderError(HeaderError): + """Exception for truncated headers.""" + pass +class EOFHeaderError(HeaderError): + """Exception for end of file headers.""" + pass +class InvalidHeaderError(HeaderError): + """Exception for invalid headers.""" + pass +class SubsequentHeaderError(HeaderError): + """Exception for missing and invalid extended headers.""" + pass + +#--------------------------- +# internal stream interface +#--------------------------- +class _LowLevelFile(object): + """Low-level file object. Supports reading and writing. + It is used instead of a regular file object for streaming + access. + """ + + def __init__(self, name, mode): + mode = { + "r": os.O_RDONLY, + "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + }[mode] + if hasattr(os, "O_BINARY"): + mode |= os.O_BINARY + self.fd = os.open(name, mode, 0o666) + + def close(self): + os.close(self.fd) + + def read(self, size): + return os.read(self.fd, size) + + def write(self, s): + os.write(self.fd, s) + +class _Stream(object): + """Class that serves as an adapter between TarFile and + a stream-like object. The stream-like object only + needs to have a read() or write() method and is accessed + blockwise. Use of gzip or bzip2 compression is possible. + A stream-like object could be for example: sys.stdin, + sys.stdout, a socket, a tape device etc. + + _Stream is intended to be used only internally. + """ + + def __init__(self, name, mode, comptype, fileobj, bufsize): + """Construct a _Stream object. + """ + self._extfileobj = True + if fileobj is None: + fileobj = _LowLevelFile(name, mode) + self._extfileobj = False + + if comptype == '*': + # Enable transparent compression detection for the + # stream interface + fileobj = _StreamProxy(fileobj) + comptype = fileobj.getcomptype() + + self.name = name or "" + self.mode = mode + self.comptype = comptype + self.fileobj = fileobj + self.bufsize = bufsize + self.buf = b"" + self.pos = 0 + self.closed = False + + try: + if comptype == "gz": + try: + import zlib + except ImportError: + raise CompressionError("zlib module is not available") + self.zlib = zlib + self.crc = zlib.crc32(b"") + if mode == "r": + self._init_read_gz() + else: + self._init_write_gz() + + if comptype == "bz2": + try: + import bz2 + except ImportError: + raise CompressionError("bz2 module is not available") + if mode == "r": + self.dbuf = b"" + self.cmp = bz2.BZ2Decompressor() + else: + self.cmp = bz2.BZ2Compressor() + except: + if not self._extfileobj: + self.fileobj.close() + self.closed = True + raise + + def __del__(self): + if hasattr(self, "closed") and not self.closed: + self.close() + + def _init_write_gz(self): + """Initialize for writing with gzip compression. + """ + self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED, + -self.zlib.MAX_WBITS, + self.zlib.DEF_MEM_LEVEL, + 0) + timestamp = struct.pack(" self.bufsize: + self.fileobj.write(self.buf[:self.bufsize]) + self.buf = self.buf[self.bufsize:] + + def close(self): + """Close the _Stream object. No operation should be + done on it afterwards. + """ + if self.closed: + return + + if self.mode == "w" and self.comptype != "tar": + self.buf += self.cmp.flush() + + if self.mode == "w" and self.buf: + self.fileobj.write(self.buf) + self.buf = b"" + if self.comptype == "gz": + # The native zlib crc is an unsigned 32-bit integer, but + # the Python wrapper implicitly casts that to a signed C + # long. So, on a 32-bit box self.crc may "look negative", + # while the same crc on a 64-bit box may "look positive". + # To avoid irksome warnings from the `struct` module, force + # it to look positive on all boxes. + self.fileobj.write(struct.pack("= 0: + blocks, remainder = divmod(pos - self.pos, self.bufsize) + for i in range(blocks): + self.read(self.bufsize) + self.read(remainder) + else: + raise StreamError("seeking backwards is not allowed") + return self.pos + + def read(self, size=None): + """Return the next size number of bytes from the stream. + If size is not defined, return all bytes of the stream + up to EOF. + """ + if size is None: + t = [] + while True: + buf = self._read(self.bufsize) + if not buf: + break + t.append(buf) + buf = "".join(t) + else: + buf = self._read(size) + self.pos += len(buf) + return buf + + def _read(self, size): + """Return size bytes from the stream. + """ + if self.comptype == "tar": + return self.__read(size) + + c = len(self.dbuf) + while c < size: + buf = self.__read(self.bufsize) + if not buf: + break + try: + buf = self.cmp.decompress(buf) + except IOError: + raise ReadError("invalid compressed data") + self.dbuf += buf + c += len(buf) + buf = self.dbuf[:size] + self.dbuf = self.dbuf[size:] + return buf + + def __read(self, size): + """Return size bytes from stream. If internal buffer is empty, + read another block from the stream. + """ + c = len(self.buf) + while c < size: + buf = self.fileobj.read(self.bufsize) + if not buf: + break + self.buf += buf + c += len(buf) + buf = self.buf[:size] + self.buf = self.buf[size:] + return buf +# class _Stream + +class _StreamProxy(object): + """Small proxy class that enables transparent compression + detection for the Stream interface (mode 'r|*'). + """ + + def __init__(self, fileobj): + self.fileobj = fileobj + self.buf = self.fileobj.read(BLOCKSIZE) + + def read(self, size): + self.read = self.fileobj.read + return self.buf + + def getcomptype(self): + if self.buf.startswith(b"\037\213\010"): + return "gz" + if self.buf.startswith(b"BZh91"): + return "bz2" + return "tar" + + def close(self): + self.fileobj.close() +# class StreamProxy + +class _BZ2Proxy(object): + """Small proxy class that enables external file object + support for "r:bz2" and "w:bz2" modes. This is actually + a workaround for a limitation in bz2 module's BZ2File + class which (unlike gzip.GzipFile) has no support for + a file object argument. + """ + + blocksize = 16 * 1024 + + def __init__(self, fileobj, mode): + self.fileobj = fileobj + self.mode = mode + self.name = getattr(self.fileobj, "name", None) + self.init() + + def init(self): + import bz2 + self.pos = 0 + if self.mode == "r": + self.bz2obj = bz2.BZ2Decompressor() + self.fileobj.seek(0) + self.buf = b"" + else: + self.bz2obj = bz2.BZ2Compressor() + + def read(self, size): + x = len(self.buf) + while x < size: + raw = self.fileobj.read(self.blocksize) + if not raw: + break + data = self.bz2obj.decompress(raw) + self.buf += data + x += len(data) + + buf = self.buf[:size] + self.buf = self.buf[size:] + self.pos += len(buf) + return buf + + def seek(self, pos): + if pos < self.pos: + self.init() + self.read(pos - self.pos) + + def tell(self): + return self.pos + + def write(self, data): + self.pos += len(data) + raw = self.bz2obj.compress(data) + self.fileobj.write(raw) + + def close(self): + if self.mode == "w": + raw = self.bz2obj.flush() + self.fileobj.write(raw) +# class _BZ2Proxy + +#------------------------ +# Extraction file object +#------------------------ +class _FileInFile(object): + """A thin wrapper around an existing file object that + provides a part of its data as an individual file + object. + """ + + def __init__(self, fileobj, offset, size, blockinfo=None): + self.fileobj = fileobj + self.offset = offset + self.size = size + self.position = 0 + + if blockinfo is None: + blockinfo = [(0, size)] + + # Construct a map with data and zero blocks. + self.map_index = 0 + self.map = [] + lastpos = 0 + realpos = self.offset + for offset, size in blockinfo: + if offset > lastpos: + self.map.append((False, lastpos, offset, None)) + self.map.append((True, offset, offset + size, realpos)) + realpos += size + lastpos = offset + size + if lastpos < self.size: + self.map.append((False, lastpos, self.size, None)) + + def seekable(self): + if not hasattr(self.fileobj, "seekable"): + # XXX gzip.GzipFile and bz2.BZ2File + return True + return self.fileobj.seekable() + + def tell(self): + """Return the current file position. + """ + return self.position + + def seek(self, position): + """Seek to a position in the file. + """ + self.position = position + + def read(self, size=None): + """Read data from the file. + """ + if size is None: + size = self.size - self.position + else: + size = min(size, self.size - self.position) + + buf = b"" + while size > 0: + while True: + data, start, stop, offset = self.map[self.map_index] + if start <= self.position < stop: + break + else: + self.map_index += 1 + if self.map_index == len(self.map): + self.map_index = 0 + length = min(size, stop - self.position) + if data: + self.fileobj.seek(offset + (self.position - start)) + buf += self.fileobj.read(length) + else: + buf += NUL * length + size -= length + self.position += length + return buf +#class _FileInFile + + +class ExFileObject(object): + """File-like object for reading an archive member. + Is returned by TarFile.extractfile(). + """ + blocksize = 1024 + + def __init__(self, tarfile, tarinfo): + self.fileobj = _FileInFile(tarfile.fileobj, + tarinfo.offset_data, + tarinfo.size, + tarinfo.sparse) + self.name = tarinfo.name + self.mode = "r" + self.closed = False + self.size = tarinfo.size + + self.position = 0 + self.buffer = b"" + + def readable(self): + return True + + def writable(self): + return False + + def seekable(self): + return self.fileobj.seekable() + + def read(self, size=None): + """Read at most size bytes from the file. If size is not + present or None, read all data until EOF is reached. + """ + if self.closed: + raise ValueError("I/O operation on closed file") + + buf = b"" + if self.buffer: + if size is None: + buf = self.buffer + self.buffer = b"" + else: + buf = self.buffer[:size] + self.buffer = self.buffer[size:] + + if size is None: + buf += self.fileobj.read() + else: + buf += self.fileobj.read(size - len(buf)) + + self.position += len(buf) + return buf + + # XXX TextIOWrapper uses the read1() method. + read1 = read + + def readline(self, size=-1): + """Read one entire line from the file. If size is present + and non-negative, return a string with at most that + size, which may be an incomplete line. + """ + if self.closed: + raise ValueError("I/O operation on closed file") + + pos = self.buffer.find(b"\n") + 1 + if pos == 0: + # no newline found. + while True: + buf = self.fileobj.read(self.blocksize) + self.buffer += buf + if not buf or b"\n" in buf: + pos = self.buffer.find(b"\n") + 1 + if pos == 0: + # no newline found. + pos = len(self.buffer) + break + + if size != -1: + pos = min(size, pos) + + buf = self.buffer[:pos] + self.buffer = self.buffer[pos:] + self.position += len(buf) + return buf + + def readlines(self): + """Return a list with all remaining lines. + """ + result = [] + while True: + line = self.readline() + if not line: break + result.append(line) + return result + + def tell(self): + """Return the current file position. + """ + if self.closed: + raise ValueError("I/O operation on closed file") + + return self.position + + def seek(self, pos, whence=os.SEEK_SET): + """Seek to a position in the file. + """ + if self.closed: + raise ValueError("I/O operation on closed file") + + if whence == os.SEEK_SET: + self.position = min(max(pos, 0), self.size) + elif whence == os.SEEK_CUR: + if pos < 0: + self.position = max(self.position + pos, 0) + else: + self.position = min(self.position + pos, self.size) + elif whence == os.SEEK_END: + self.position = max(min(self.size + pos, self.size), 0) + else: + raise ValueError("Invalid argument") + + self.buffer = b"" + self.fileobj.seek(self.position) + + def close(self): + """Close the file object. + """ + self.closed = True + + def __iter__(self): + """Get an iterator over the file's lines. + """ + while True: + line = self.readline() + if not line: + break + yield line +#class ExFileObject + +#------------------ +# Exported Classes +#------------------ +class TarInfo(object): + """Informational class which holds the details about an + archive member given by a tar header block. + TarInfo objects are returned by TarFile.getmember(), + TarFile.getmembers() and TarFile.gettarinfo() and are + usually created internally. + """ + + __slots__ = ("name", "mode", "uid", "gid", "size", "mtime", + "chksum", "type", "linkname", "uname", "gname", + "devmajor", "devminor", + "offset", "offset_data", "pax_headers", "sparse", + "tarfile", "_sparse_structs", "_link_target") + + def __init__(self, name=""): + """Construct a TarInfo object. name is the optional name + of the member. + """ + self.name = name # member name + self.mode = 0o644 # file permissions + self.uid = 0 # user id + self.gid = 0 # group id + self.size = 0 # file size + self.mtime = 0 # modification time + self.chksum = 0 # header checksum + self.type = REGTYPE # member type + self.linkname = "" # link name + self.uname = "" # user name + self.gname = "" # group name + self.devmajor = 0 # device major number + self.devminor = 0 # device minor number + + self.offset = 0 # the tar header starts here + self.offset_data = 0 # the file's data starts here + + self.sparse = None # sparse member information + self.pax_headers = {} # pax header information + + # In pax headers the "name" and "linkname" field are called + # "path" and "linkpath". + def _getpath(self): + return self.name + def _setpath(self, name): + self.name = name + path = property(_getpath, _setpath) + + def _getlinkpath(self): + return self.linkname + def _setlinkpath(self, linkname): + self.linkname = linkname + linkpath = property(_getlinkpath, _setlinkpath) + + def __repr__(self): + return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self)) + + def get_info(self): + """Return the TarInfo's attributes as a dictionary. + """ + info = { + "name": self.name, + "mode": self.mode & 0o7777, + "uid": self.uid, + "gid": self.gid, + "size": self.size, + "mtime": self.mtime, + "chksum": self.chksum, + "type": self.type, + "linkname": self.linkname, + "uname": self.uname, + "gname": self.gname, + "devmajor": self.devmajor, + "devminor": self.devminor + } + + if info["type"] == DIRTYPE and not info["name"].endswith("/"): + info["name"] += "/" + + return info + + def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"): + """Return a tar header as a string of 512 byte blocks. + """ + info = self.get_info() + + if format == USTAR_FORMAT: + return self.create_ustar_header(info, encoding, errors) + elif format == GNU_FORMAT: + return self.create_gnu_header(info, encoding, errors) + elif format == PAX_FORMAT: + return self.create_pax_header(info, encoding) + else: + raise ValueError("invalid format") + + def create_ustar_header(self, info, encoding, errors): + """Return the object as a ustar header block. + """ + info["magic"] = POSIX_MAGIC + + if len(info["linkname"]) > LENGTH_LINK: + raise ValueError("linkname is too long") + + if len(info["name"]) > LENGTH_NAME: + info["prefix"], info["name"] = self._posix_split_name(info["name"]) + + return self._create_header(info, USTAR_FORMAT, encoding, errors) + + def create_gnu_header(self, info, encoding, errors): + """Return the object as a GNU header block sequence. + """ + info["magic"] = GNU_MAGIC + + buf = b"" + if len(info["linkname"]) > LENGTH_LINK: + buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors) + + if len(info["name"]) > LENGTH_NAME: + buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors) + + return buf + self._create_header(info, GNU_FORMAT, encoding, errors) + + def create_pax_header(self, info, encoding): + """Return the object as a ustar header block. If it cannot be + represented this way, prepend a pax extended header sequence + with supplement information. + """ + info["magic"] = POSIX_MAGIC + pax_headers = self.pax_headers.copy() + + # Test string fields for values that exceed the field length or cannot + # be represented in ASCII encoding. + for name, hname, length in ( + ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK), + ("uname", "uname", 32), ("gname", "gname", 32)): + + if hname in pax_headers: + # The pax header has priority. + continue + + # Try to encode the string as ASCII. + try: + info[name].encode("ascii", "strict") + except UnicodeEncodeError: + pax_headers[hname] = info[name] + continue + + if len(info[name]) > length: + pax_headers[hname] = info[name] + + # Test number fields for values that exceed the field limit or values + # that like to be stored as float. + for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)): + if name in pax_headers: + # The pax header has priority. Avoid overflow. + info[name] = 0 + continue + + val = info[name] + if not 0 <= val < 8 ** (digits - 1) or isinstance(val, float): + pax_headers[name] = str(val) + info[name] = 0 + + # Create a pax extended header if necessary. + if pax_headers: + buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding) + else: + buf = b"" + + return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace") + + @classmethod + def create_pax_global_header(cls, pax_headers): + """Return the object as a pax global header block sequence. + """ + return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf8") + + def _posix_split_name(self, name): + """Split a name longer than 100 chars into a prefix + and a name part. + """ + prefix = name[:LENGTH_PREFIX + 1] + while prefix and prefix[-1] != "/": + prefix = prefix[:-1] + + name = name[len(prefix):] + prefix = prefix[:-1] + + if not prefix or len(name) > LENGTH_NAME: + raise ValueError("name is too long") + return prefix, name + + @staticmethod + def _create_header(info, format, encoding, errors): + """Return a header block. info is a dictionary with file + information, format must be one of the *_FORMAT constants. + """ + parts = [ + stn(info.get("name", ""), 100, encoding, errors), + itn(info.get("mode", 0) & 0o7777, 8, format), + itn(info.get("uid", 0), 8, format), + itn(info.get("gid", 0), 8, format), + itn(info.get("size", 0), 12, format), + itn(info.get("mtime", 0), 12, format), + b" ", # checksum field + info.get("type", REGTYPE), + stn(info.get("linkname", ""), 100, encoding, errors), + info.get("magic", POSIX_MAGIC), + stn(info.get("uname", ""), 32, encoding, errors), + stn(info.get("gname", ""), 32, encoding, errors), + itn(info.get("devmajor", 0), 8, format), + itn(info.get("devminor", 0), 8, format), + stn(info.get("prefix", ""), 155, encoding, errors) + ] + + buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts)) + chksum = calc_chksums(buf[-BLOCKSIZE:])[0] + buf = buf[:-364] + ("%06o\0" % chksum).encode("ascii") + buf[-357:] + return buf + + @staticmethod + def _create_payload(payload): + """Return the string payload filled with zero bytes + up to the next 512 byte border. + """ + blocks, remainder = divmod(len(payload), BLOCKSIZE) + if remainder > 0: + payload += (BLOCKSIZE - remainder) * NUL + return payload + + @classmethod + def _create_gnu_long_header(cls, name, type, encoding, errors): + """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence + for name. + """ + name = name.encode(encoding, errors) + NUL + + info = {} + info["name"] = "././@LongLink" + info["type"] = type + info["size"] = len(name) + info["magic"] = GNU_MAGIC + + # create extended header + name blocks. + return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \ + cls._create_payload(name) + + @classmethod + def _create_pax_generic_header(cls, pax_headers, type, encoding): + """Return a POSIX.1-2008 extended or global header sequence + that contains a list of keyword, value pairs. The values + must be strings. + """ + # Check if one of the fields contains surrogate characters and thereby + # forces hdrcharset=BINARY, see _proc_pax() for more information. + binary = False + for keyword, value in pax_headers.items(): + try: + value.encode("utf8", "strict") + except UnicodeEncodeError: + binary = True + break + + records = b"" + if binary: + # Put the hdrcharset field at the beginning of the header. + records += b"21 hdrcharset=BINARY\n" + + for keyword, value in pax_headers.items(): + keyword = keyword.encode("utf8") + if binary: + # Try to restore the original byte representation of `value'. + # Needless to say, that the encoding must match the string. + value = value.encode(encoding, "surrogateescape") + else: + value = value.encode("utf8") + + l = len(keyword) + len(value) + 3 # ' ' + '=' + '\n' + n = p = 0 + while True: + n = l + len(str(p)) + if n == p: + break + p = n + records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n" + + # We use a hardcoded "././@PaxHeader" name like star does + # instead of the one that POSIX recommends. + info = {} + info["name"] = "././@PaxHeader" + info["type"] = type + info["size"] = len(records) + info["magic"] = POSIX_MAGIC + + # Create pax header + record blocks. + return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \ + cls._create_payload(records) + + @classmethod + def frombuf(cls, buf, encoding, errors): + """Construct a TarInfo object from a 512 byte bytes object. + """ + if len(buf) == 0: + raise EmptyHeaderError("empty header") + if len(buf) != BLOCKSIZE: + raise TruncatedHeaderError("truncated header") + if buf.count(NUL) == BLOCKSIZE: + raise EOFHeaderError("end of file header") + + chksum = nti(buf[148:156]) + if chksum not in calc_chksums(buf): + raise InvalidHeaderError("bad checksum") + + obj = cls() + obj.name = nts(buf[0:100], encoding, errors) + obj.mode = nti(buf[100:108]) + obj.uid = nti(buf[108:116]) + obj.gid = nti(buf[116:124]) + obj.size = nti(buf[124:136]) + obj.mtime = nti(buf[136:148]) + obj.chksum = chksum + obj.type = buf[156:157] + obj.linkname = nts(buf[157:257], encoding, errors) + obj.uname = nts(buf[265:297], encoding, errors) + obj.gname = nts(buf[297:329], encoding, errors) + obj.devmajor = nti(buf[329:337]) + obj.devminor = nti(buf[337:345]) + prefix = nts(buf[345:500], encoding, errors) + + # Old V7 tar format represents a directory as a regular + # file with a trailing slash. + if obj.type == AREGTYPE and obj.name.endswith("/"): + obj.type = DIRTYPE + + # The old GNU sparse format occupies some of the unused + # space in the buffer for up to 4 sparse structures. + # Save the them for later processing in _proc_sparse(). + if obj.type == GNUTYPE_SPARSE: + pos = 386 + structs = [] + for i in range(4): + try: + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) + except ValueError: + break + structs.append((offset, numbytes)) + pos += 24 + isextended = bool(buf[482]) + origsize = nti(buf[483:495]) + obj._sparse_structs = (structs, isextended, origsize) + + # Remove redundant slashes from directories. + if obj.isdir(): + obj.name = obj.name.rstrip("/") + + # Reconstruct a ustar longname. + if prefix and obj.type not in GNU_TYPES: + obj.name = prefix + "/" + obj.name + return obj + + @classmethod + def fromtarfile(cls, tarfile): + """Return the next TarInfo object from TarFile object + tarfile. + """ + buf = tarfile.fileobj.read(BLOCKSIZE) + obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors) + obj.offset = tarfile.fileobj.tell() - BLOCKSIZE + return obj._proc_member(tarfile) + + #-------------------------------------------------------------------------- + # The following are methods that are called depending on the type of a + # member. The entry point is _proc_member() which can be overridden in a + # subclass to add custom _proc_*() methods. A _proc_*() method MUST + # implement the following + # operations: + # 1. Set self.offset_data to the position where the data blocks begin, + # if there is data that follows. + # 2. Set tarfile.offset to the position where the next member's header will + # begin. + # 3. Return self or another valid TarInfo object. + def _proc_member(self, tarfile): + """Choose the right processing method depending on + the type and call it. + """ + if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK): + return self._proc_gnulong(tarfile) + elif self.type == GNUTYPE_SPARSE: + return self._proc_sparse(tarfile) + elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE): + return self._proc_pax(tarfile) + else: + return self._proc_builtin(tarfile) + + def _proc_builtin(self, tarfile): + """Process a builtin type or an unknown type which + will be treated as a regular file. + """ + self.offset_data = tarfile.fileobj.tell() + offset = self.offset_data + if self.isreg() or self.type not in SUPPORTED_TYPES: + # Skip the following data blocks. + offset += self._block(self.size) + tarfile.offset = offset + + # Patch the TarInfo object with saved global + # header information. + self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors) + + return self + + def _proc_gnulong(self, tarfile): + """Process the blocks that hold a GNU longname + or longlink member. + """ + buf = tarfile.fileobj.read(self._block(self.size)) + + # Fetch the next header and process it. + try: + next = self.fromtarfile(tarfile) + except HeaderError: + raise SubsequentHeaderError("missing or bad subsequent header") + + # Patch the TarInfo object from the next header with + # the longname information. + next.offset = self.offset + if self.type == GNUTYPE_LONGNAME: + next.name = nts(buf, tarfile.encoding, tarfile.errors) + elif self.type == GNUTYPE_LONGLINK: + next.linkname = nts(buf, tarfile.encoding, tarfile.errors) + + return next + + def _proc_sparse(self, tarfile): + """Process a GNU sparse header plus extra headers. + """ + # We already collected some sparse structures in frombuf(). + structs, isextended, origsize = self._sparse_structs + del self._sparse_structs + + # Collect sparse structures from extended header blocks. + while isextended: + buf = tarfile.fileobj.read(BLOCKSIZE) + pos = 0 + for i in range(21): + try: + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) + except ValueError: + break + if offset and numbytes: + structs.append((offset, numbytes)) + pos += 24 + isextended = bool(buf[504]) + self.sparse = structs + + self.offset_data = tarfile.fileobj.tell() + tarfile.offset = self.offset_data + self._block(self.size) + self.size = origsize + return self + + def _proc_pax(self, tarfile): + """Process an extended or global header as described in + POSIX.1-2008. + """ + # Read the header information. + buf = tarfile.fileobj.read(self._block(self.size)) + + # A pax header stores supplemental information for either + # the following file (extended) or all following files + # (global). + if self.type == XGLTYPE: + pax_headers = tarfile.pax_headers + else: + pax_headers = tarfile.pax_headers.copy() + + # Check if the pax header contains a hdrcharset field. This tells us + # the encoding of the path, linkpath, uname and gname fields. Normally, + # these fields are UTF-8 encoded but since POSIX.1-2008 tar + # implementations are allowed to store them as raw binary strings if + # the translation to UTF-8 fails. + match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf) + if match is not None: + pax_headers["hdrcharset"] = match.group(1).decode("utf8") + + # For the time being, we don't care about anything other than "BINARY". + # The only other value that is currently allowed by the standard is + # "ISO-IR 10646 2000 UTF-8" in other words UTF-8. + hdrcharset = pax_headers.get("hdrcharset") + if hdrcharset == "BINARY": + encoding = tarfile.encoding + else: + encoding = "utf8" + + # Parse pax header information. A record looks like that: + # "%d %s=%s\n" % (length, keyword, value). length is the size + # of the complete record including the length field itself and + # the newline. keyword and value are both UTF-8 encoded strings. + regex = re.compile(br"(\d+) ([^=]+)=") + pos = 0 + while True: + match = regex.match(buf, pos) + if not match: + break + + length, keyword = match.groups() + length = int(length) + value = buf[match.end(2) + 1:match.start(1) + length - 1] + + # Normally, we could just use "utf8" as the encoding and "strict" + # as the error handler, but we better not take the risk. For + # example, GNU tar <= 1.23 is known to store filenames it cannot + # translate to UTF-8 as raw strings (unfortunately without a + # hdrcharset=BINARY header). + # We first try the strict standard encoding, and if that fails we + # fall back on the user's encoding and error handler. + keyword = self._decode_pax_field(keyword, "utf8", "utf8", + tarfile.errors) + if keyword in PAX_NAME_FIELDS: + value = self._decode_pax_field(value, encoding, tarfile.encoding, + tarfile.errors) + else: + value = self._decode_pax_field(value, "utf8", "utf8", + tarfile.errors) + + pax_headers[keyword] = value + pos += length + + # Fetch the next header. + try: + next = self.fromtarfile(tarfile) + except HeaderError: + raise SubsequentHeaderError("missing or bad subsequent header") + + # Process GNU sparse information. + if "GNU.sparse.map" in pax_headers: + # GNU extended sparse format version 0.1. + self._proc_gnusparse_01(next, pax_headers) + + elif "GNU.sparse.size" in pax_headers: + # GNU extended sparse format version 0.0. + self._proc_gnusparse_00(next, pax_headers, buf) + + elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0": + # GNU extended sparse format version 1.0. + self._proc_gnusparse_10(next, pax_headers, tarfile) + + if self.type in (XHDTYPE, SOLARIS_XHDTYPE): + # Patch the TarInfo object with the extended header info. + next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors) + next.offset = self.offset + + if "size" in pax_headers: + # If the extended header replaces the size field, + # we need to recalculate the offset where the next + # header starts. + offset = next.offset_data + if next.isreg() or next.type not in SUPPORTED_TYPES: + offset += next._block(next.size) + tarfile.offset = offset + + return next + + def _proc_gnusparse_00(self, next, pax_headers, buf): + """Process a GNU tar extended sparse header, version 0.0. + """ + offsets = [] + for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf): + offsets.append(int(match.group(1))) + numbytes = [] + for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf): + numbytes.append(int(match.group(1))) + next.sparse = list(zip(offsets, numbytes)) + + def _proc_gnusparse_01(self, next, pax_headers): + """Process a GNU tar extended sparse header, version 0.1. + """ + sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")] + next.sparse = list(zip(sparse[::2], sparse[1::2])) + + def _proc_gnusparse_10(self, next, pax_headers, tarfile): + """Process a GNU tar extended sparse header, version 1.0. + """ + fields = None + sparse = [] + buf = tarfile.fileobj.read(BLOCKSIZE) + fields, buf = buf.split(b"\n", 1) + fields = int(fields) + while len(sparse) < fields * 2: + if b"\n" not in buf: + buf += tarfile.fileobj.read(BLOCKSIZE) + number, buf = buf.split(b"\n", 1) + sparse.append(int(number)) + next.offset_data = tarfile.fileobj.tell() + next.sparse = list(zip(sparse[::2], sparse[1::2])) + + def _apply_pax_info(self, pax_headers, encoding, errors): + """Replace fields with supplemental information from a previous + pax extended or global header. + """ + for keyword, value in pax_headers.items(): + if keyword == "GNU.sparse.name": + setattr(self, "path", value) + elif keyword == "GNU.sparse.size": + setattr(self, "size", int(value)) + elif keyword == "GNU.sparse.realsize": + setattr(self, "size", int(value)) + elif keyword in PAX_FIELDS: + if keyword in PAX_NUMBER_FIELDS: + try: + value = PAX_NUMBER_FIELDS[keyword](value) + except ValueError: + value = 0 + if keyword == "path": + value = value.rstrip("/") + setattr(self, keyword, value) + + self.pax_headers = pax_headers.copy() + + def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors): + """Decode a single field from a pax record. + """ + try: + return value.decode(encoding, "strict") + except UnicodeDecodeError: + return value.decode(fallback_encoding, fallback_errors) + + def _block(self, count): + """Round up a byte count by BLOCKSIZE and return it, + e.g. _block(834) => 1024. + """ + blocks, remainder = divmod(count, BLOCKSIZE) + if remainder: + blocks += 1 + return blocks * BLOCKSIZE + + def isreg(self): + return self.type in REGULAR_TYPES + def isfile(self): + return self.isreg() + def isdir(self): + return self.type == DIRTYPE + def issym(self): + return self.type == SYMTYPE + def islnk(self): + return self.type == LNKTYPE + def ischr(self): + return self.type == CHRTYPE + def isblk(self): + return self.type == BLKTYPE + def isfifo(self): + return self.type == FIFOTYPE + def issparse(self): + return self.sparse is not None + def isdev(self): + return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE) +# class TarInfo + +class TarFile(object): + """The TarFile Class provides an interface to tar archives. + """ + + debug = 0 # May be set from 0 (no msgs) to 3 (all msgs) + + dereference = False # If true, add content of linked file to the + # tar file, else the link. + + ignore_zeros = False # If true, skips empty or invalid blocks and + # continues processing. + + errorlevel = 1 # If 0, fatal errors only appear in debug + # messages (if debug >= 0). If > 0, errors + # are passed to the caller as exceptions. + + format = DEFAULT_FORMAT # The format to use when creating an archive. + + encoding = ENCODING # Encoding for 8-bit character strings. + + errors = None # Error handler for unicode conversion. + + tarinfo = TarInfo # The default TarInfo class to use. + + fileobject = ExFileObject # The default ExFileObject class to use. + + def __init__(self, name=None, mode="r", fileobj=None, format=None, + tarinfo=None, dereference=None, ignore_zeros=None, encoding=None, + errors="surrogateescape", pax_headers=None, debug=None, errorlevel=None): + """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to + read from an existing archive, 'a' to append data to an existing + file or 'w' to create a new file overwriting an existing one. `mode' + defaults to 'r'. + If `fileobj' is given, it is used for reading or writing data. If it + can be determined, `mode' is overridden by `fileobj's mode. + `fileobj' is not closed, when TarFile is closed. + """ + if len(mode) > 1 or mode not in "raw": + raise ValueError("mode must be 'r', 'a' or 'w'") + self.mode = mode + self._mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode] + + if not fileobj: + if self.mode == "a" and not os.path.exists(name): + # Create nonexistent files in append mode. + self.mode = "w" + self._mode = "wb" + fileobj = bltn_open(name, self._mode) + self._extfileobj = False + else: + if name is None and hasattr(fileobj, "name"): + name = fileobj.name + if hasattr(fileobj, "mode"): + self._mode = fileobj.mode + self._extfileobj = True + self.name = os.path.abspath(name) if name else None + self.fileobj = fileobj + + # Init attributes. + if format is not None: + self.format = format + if tarinfo is not None: + self.tarinfo = tarinfo + if dereference is not None: + self.dereference = dereference + if ignore_zeros is not None: + self.ignore_zeros = ignore_zeros + if encoding is not None: + self.encoding = encoding + self.errors = errors + + if pax_headers is not None and self.format == PAX_FORMAT: + self.pax_headers = pax_headers + else: + self.pax_headers = {} + + if debug is not None: + self.debug = debug + if errorlevel is not None: + self.errorlevel = errorlevel + + # Init datastructures. + self.closed = False + self.members = [] # list of members as TarInfo objects + self._loaded = False # flag if all members have been read + self.offset = self.fileobj.tell() + # current position in the archive file + self.inodes = {} # dictionary caching the inodes of + # archive members already added + + try: + if self.mode == "r": + self.firstmember = None + self.firstmember = self.next() + + if self.mode == "a": + # Move to the end of the archive, + # before the first empty block. + while True: + self.fileobj.seek(self.offset) + try: + tarinfo = self.tarinfo.fromtarfile(self) + self.members.append(tarinfo) + except EOFHeaderError: + self.fileobj.seek(self.offset) + break + except HeaderError as e: + raise ReadError(str(e)) + + if self.mode in "aw": + self._loaded = True + + if self.pax_headers: + buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy()) + self.fileobj.write(buf) + self.offset += len(buf) + except: + if not self._extfileobj: + self.fileobj.close() + self.closed = True + raise + + #-------------------------------------------------------------------------- + # Below are the classmethods which act as alternate constructors to the + # TarFile class. The open() method is the only one that is needed for + # public use; it is the "super"-constructor and is able to select an + # adequate "sub"-constructor for a particular compression using the mapping + # from OPEN_METH. + # + # This concept allows one to subclass TarFile without losing the comfort of + # the super-constructor. A sub-constructor is registered and made available + # by adding it to the mapping in OPEN_METH. + + @classmethod + def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs): + """Open a tar archive for reading, writing or appending. Return + an appropriate TarFile class. + + mode: + 'r' or 'r:*' open for reading with transparent compression + 'r:' open for reading exclusively uncompressed + 'r:gz' open for reading with gzip compression + 'r:bz2' open for reading with bzip2 compression + 'a' or 'a:' open for appending, creating the file if necessary + 'w' or 'w:' open for writing without compression + 'w:gz' open for writing with gzip compression + 'w:bz2' open for writing with bzip2 compression + + 'r|*' open a stream of tar blocks with transparent compression + 'r|' open an uncompressed stream of tar blocks for reading + 'r|gz' open a gzip compressed stream of tar blocks + 'r|bz2' open a bzip2 compressed stream of tar blocks + 'w|' open an uncompressed stream for writing + 'w|gz' open a gzip compressed stream for writing + 'w|bz2' open a bzip2 compressed stream for writing + """ + + if not name and not fileobj: + raise ValueError("nothing to open") + + if mode in ("r", "r:*"): + # Find out which *open() is appropriate for opening the file. + for comptype in cls.OPEN_METH: + func = getattr(cls, cls.OPEN_METH[comptype]) + if fileobj is not None: + saved_pos = fileobj.tell() + try: + return func(name, "r", fileobj, **kwargs) + except (ReadError, CompressionError) as e: + if fileobj is not None: + fileobj.seek(saved_pos) + continue + raise ReadError("file could not be opened successfully") + + elif ":" in mode: + filemode, comptype = mode.split(":", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + # Select the *open() function according to + # given compression. + if comptype in cls.OPEN_METH: + func = getattr(cls, cls.OPEN_METH[comptype]) + else: + raise CompressionError("unknown compression type %r" % comptype) + return func(name, filemode, fileobj, **kwargs) + + elif "|" in mode: + filemode, comptype = mode.split("|", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + if filemode not in "rw": + raise ValueError("mode must be 'r' or 'w'") + + stream = _Stream(name, filemode, comptype, fileobj, bufsize) + try: + t = cls(name, filemode, stream, **kwargs) + except: + stream.close() + raise + t._extfileobj = False + return t + + elif mode in "aw": + return cls.taropen(name, mode, fileobj, **kwargs) + + raise ValueError("undiscernible mode") + + @classmethod + def taropen(cls, name, mode="r", fileobj=None, **kwargs): + """Open uncompressed tar archive name for reading or writing. + """ + if len(mode) > 1 or mode not in "raw": + raise ValueError("mode must be 'r', 'a' or 'w'") + return cls(name, mode, fileobj, **kwargs) + + @classmethod + def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs): + """Open gzip compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if len(mode) > 1 or mode not in "rw": + raise ValueError("mode must be 'r' or 'w'") + + try: + import gzip + gzip.GzipFile + except (ImportError, AttributeError): + raise CompressionError("gzip module is not available") + + extfileobj = fileobj is not None + try: + fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj) + t = cls.taropen(name, mode, fileobj, **kwargs) + except IOError: + if not extfileobj and fileobj is not None: + fileobj.close() + if fileobj is None: + raise + raise ReadError("not a gzip file") + except: + if not extfileobj and fileobj is not None: + fileobj.close() + raise + t._extfileobj = extfileobj + return t + + @classmethod + def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs): + """Open bzip2 compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if len(mode) > 1 or mode not in "rw": + raise ValueError("mode must be 'r' or 'w'.") + + try: + import bz2 + except ImportError: + raise CompressionError("bz2 module is not available") + + if fileobj is not None: + fileobj = _BZ2Proxy(fileobj, mode) + else: + fileobj = bz2.BZ2File(name, mode, compresslevel=compresslevel) + + try: + t = cls.taropen(name, mode, fileobj, **kwargs) + except (IOError, EOFError): + fileobj.close() + raise ReadError("not a bzip2 file") + t._extfileobj = False + return t + + # All *open() methods are registered here. + OPEN_METH = { + "tar": "taropen", # uncompressed tar + "gz": "gzopen", # gzip compressed tar + "bz2": "bz2open" # bzip2 compressed tar + } + + #-------------------------------------------------------------------------- + # The public methods which TarFile provides: + + def close(self): + """Close the TarFile. In write-mode, two finishing zero blocks are + appended to the archive. + """ + if self.closed: + return + + if self.mode in "aw": + self.fileobj.write(NUL * (BLOCKSIZE * 2)) + self.offset += (BLOCKSIZE * 2) + # fill up the end with zero-blocks + # (like option -b20 for tar does) + blocks, remainder = divmod(self.offset, RECORDSIZE) + if remainder > 0: + self.fileobj.write(NUL * (RECORDSIZE - remainder)) + + if not self._extfileobj: + self.fileobj.close() + self.closed = True + + def getmember(self, name): + """Return a TarInfo object for member `name'. If `name' can not be + found in the archive, KeyError is raised. If a member occurs more + than once in the archive, its last occurrence is assumed to be the + most up-to-date version. + """ + tarinfo = self._getmember(name) + if tarinfo is None: + raise KeyError("filename %r not found" % name) + return tarinfo + + def getmembers(self): + """Return the members of the archive as a list of TarInfo objects. The + list has the same order as the members in the archive. + """ + self._check() + if not self._loaded: # if we want to obtain a list of + self._load() # all members, we first have to + # scan the whole archive. + return self.members + + def getnames(self): + """Return the members of the archive as a list of their names. It has + the same order as the list returned by getmembers(). + """ + return [tarinfo.name for tarinfo in self.getmembers()] + + def gettarinfo(self, name=None, arcname=None, fileobj=None): + """Create a TarInfo object for either the file `name' or the file + object `fileobj' (using os.fstat on its file descriptor). You can + modify some of the TarInfo's attributes before you add it using + addfile(). If given, `arcname' specifies an alternative name for the + file in the archive. + """ + self._check("aw") + + # When fileobj is given, replace name by + # fileobj's real name. + if fileobj is not None: + name = fileobj.name + + # Building the name of the member in the archive. + # Backward slashes are converted to forward slashes, + # Absolute paths are turned to relative paths. + if arcname is None: + arcname = name + drv, arcname = os.path.splitdrive(arcname) + arcname = arcname.replace(os.sep, "/") + arcname = arcname.lstrip("/") + + # Now, fill the TarInfo object with + # information specific for the file. + tarinfo = self.tarinfo() + tarinfo.tarfile = self + + # Use os.stat or os.lstat, depending on platform + # and if symlinks shall be resolved. + if fileobj is None: + if hasattr(os, "lstat") and not self.dereference: + statres = os.lstat(name) + else: + statres = os.stat(name) + else: + statres = os.fstat(fileobj.fileno()) + linkname = "" + + stmd = statres.st_mode + if stat.S_ISREG(stmd): + inode = (statres.st_ino, statres.st_dev) + if not self.dereference and statres.st_nlink > 1 and \ + inode in self.inodes and arcname != self.inodes[inode]: + # Is it a hardlink to an already + # archived file? + type = LNKTYPE + linkname = self.inodes[inode] + else: + # The inode is added only if its valid. + # For win32 it is always 0. + type = REGTYPE + if inode[0]: + self.inodes[inode] = arcname + elif stat.S_ISDIR(stmd): + type = DIRTYPE + elif stat.S_ISFIFO(stmd): + type = FIFOTYPE + elif stat.S_ISLNK(stmd): + type = SYMTYPE + linkname = os.readlink(name) + elif stat.S_ISCHR(stmd): + type = CHRTYPE + elif stat.S_ISBLK(stmd): + type = BLKTYPE + else: + return None + + # Fill the TarInfo object with all + # information we can get. + tarinfo.name = arcname + tarinfo.mode = stmd + tarinfo.uid = statres.st_uid + tarinfo.gid = statres.st_gid + if type == REGTYPE: + tarinfo.size = statres.st_size + else: + tarinfo.size = 0 + tarinfo.mtime = statres.st_mtime + tarinfo.type = type + tarinfo.linkname = linkname + if pwd: + try: + tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0] + except KeyError: + pass + if grp: + try: + tarinfo.gname = grp.getgrgid(tarinfo.gid)[0] + except KeyError: + pass + + if type in (CHRTYPE, BLKTYPE): + if hasattr(os, "major") and hasattr(os, "minor"): + tarinfo.devmajor = os.major(statres.st_rdev) + tarinfo.devminor = os.minor(statres.st_rdev) + return tarinfo + + def list(self, verbose=True): + """Print a table of contents to sys.stdout. If `verbose' is False, only + the names of the members are printed. If it is True, an `ls -l'-like + output is produced. + """ + self._check() + + for tarinfo in self: + if verbose: + print(filemode(tarinfo.mode), end=' ') + print("%s/%s" % (tarinfo.uname or tarinfo.uid, + tarinfo.gname or tarinfo.gid), end=' ') + if tarinfo.ischr() or tarinfo.isblk(): + print("%10s" % ("%d,%d" \ + % (tarinfo.devmajor, tarinfo.devminor)), end=' ') + else: + print("%10d" % tarinfo.size, end=' ') + print("%d-%02d-%02d %02d:%02d:%02d" \ + % time.localtime(tarinfo.mtime)[:6], end=' ') + + print(tarinfo.name + ("/" if tarinfo.isdir() else ""), end=' ') + + if verbose: + if tarinfo.issym(): + print("->", tarinfo.linkname, end=' ') + if tarinfo.islnk(): + print("link to", tarinfo.linkname, end=' ') + print() + + def add(self, name, arcname=None, recursive=True, exclude=None, filter=None): + """Add the file `name' to the archive. `name' may be any type of file + (directory, fifo, symbolic link, etc.). If given, `arcname' + specifies an alternative name for the file in the archive. + Directories are added recursively by default. This can be avoided by + setting `recursive' to False. `exclude' is a function that should + return True for each filename to be excluded. `filter' is a function + that expects a TarInfo object argument and returns the changed + TarInfo object, if it returns None the TarInfo object will be + excluded from the archive. + """ + self._check("aw") + + if arcname is None: + arcname = name + + # Exclude pathnames. + if exclude is not None: + import warnings + warnings.warn("use the filter argument instead", + DeprecationWarning, 2) + if exclude(name): + self._dbg(2, "tarfile: Excluded %r" % name) + return + + # Skip if somebody tries to archive the archive... + if self.name is not None and os.path.abspath(name) == self.name: + self._dbg(2, "tarfile: Skipped %r" % name) + return + + self._dbg(1, name) + + # Create a TarInfo object from the file. + tarinfo = self.gettarinfo(name, arcname) + + if tarinfo is None: + self._dbg(1, "tarfile: Unsupported type %r" % name) + return + + # Change or exclude the TarInfo object. + if filter is not None: + tarinfo = filter(tarinfo) + if tarinfo is None: + self._dbg(2, "tarfile: Excluded %r" % name) + return + + # Append the tar header and data to the archive. + if tarinfo.isreg(): + f = bltn_open(name, "rb") + self.addfile(tarinfo, f) + f.close() + + elif tarinfo.isdir(): + self.addfile(tarinfo) + if recursive: + for f in os.listdir(name): + self.add(os.path.join(name, f), os.path.join(arcname, f), + recursive, exclude, filter=filter) + + else: + self.addfile(tarinfo) + + def addfile(self, tarinfo, fileobj=None): + """Add the TarInfo object `tarinfo' to the archive. If `fileobj' is + given, tarinfo.size bytes are read from it and added to the archive. + You can create TarInfo objects using gettarinfo(). + On Windows platforms, `fileobj' should always be opened with mode + 'rb' to avoid irritation about the file size. + """ + self._check("aw") + + tarinfo = copy.copy(tarinfo) + + buf = tarinfo.tobuf(self.format, self.encoding, self.errors) + self.fileobj.write(buf) + self.offset += len(buf) + + # If there's data to follow, append it. + if fileobj is not None: + copyfileobj(fileobj, self.fileobj, tarinfo.size) + blocks, remainder = divmod(tarinfo.size, BLOCKSIZE) + if remainder > 0: + self.fileobj.write(NUL * (BLOCKSIZE - remainder)) + blocks += 1 + self.offset += blocks * BLOCKSIZE + + self.members.append(tarinfo) + + def extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 0o700 + # Do not set_attrs directories, as we will do that further down + self.extract(tarinfo, path, set_attrs=not tarinfo.isdir()) + + # Reverse sort directories. + directories.sort(key=lambda a: a.name) + directories.reverse() + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError as e: + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + def extract(self, member, path="", set_attrs=True): + """Extract a member from the archive to the current working directory, + using its full name. Its file information is extracted as accurately + as possible. `member' may be a filename or a TarInfo object. You can + specify a different directory using `path'. File attributes (owner, + mtime, mode) are set unless `set_attrs' is False. + """ + self._check("r") + + if isinstance(member, str): + tarinfo = self.getmember(member) + else: + tarinfo = member + + # Prepare the link target for makelink(). + if tarinfo.islnk(): + tarinfo._link_target = os.path.join(path, tarinfo.linkname) + + try: + self._extract_member(tarinfo, os.path.join(path, tarinfo.name), + set_attrs=set_attrs) + except EnvironmentError as e: + if self.errorlevel > 0: + raise + else: + if e.filename is None: + self._dbg(1, "tarfile: %s" % e.strerror) + else: + self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename)) + except ExtractError as e: + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + def extractfile(self, member): + """Extract a member from the archive as a file object. `member' may be + a filename or a TarInfo object. If `member' is a regular file, a + file-like object is returned. If `member' is a link, a file-like + object is constructed from the link's target. If `member' is none of + the above, None is returned. + The file-like object is read-only and provides the following + methods: read(), readline(), readlines(), seek() and tell() + """ + self._check("r") + + if isinstance(member, str): + tarinfo = self.getmember(member) + else: + tarinfo = member + + if tarinfo.isreg(): + return self.fileobject(self, tarinfo) + + elif tarinfo.type not in SUPPORTED_TYPES: + # If a member's type is unknown, it is treated as a + # regular file. + return self.fileobject(self, tarinfo) + + elif tarinfo.islnk() or tarinfo.issym(): + if isinstance(self.fileobj, _Stream): + # A small but ugly workaround for the case that someone tries + # to extract a (sym)link as a file-object from a non-seekable + # stream of tar blocks. + raise StreamError("cannot extract (sym)link as file object") + else: + # A (sym)link's file object is its target's file object. + return self.extractfile(self._find_link_target(tarinfo)) + else: + # If there's no data associated with the member (directory, chrdev, + # blkdev, etc.), return None instead of a file object. + return None + + def _extract_member(self, tarinfo, targetpath, set_attrs=True): + """Extract the TarInfo object tarinfo to a physical + file called targetpath. + """ + # Fetch the TarInfo object for the given name + # and build the destination pathname, replacing + # forward slashes to platform specific separators. + targetpath = targetpath.rstrip("/") + targetpath = targetpath.replace("/", os.sep) + + # Create all upper directories. + upperdirs = os.path.dirname(targetpath) + if upperdirs and not os.path.exists(upperdirs): + # Create directories that are not part of the archive with + # default permissions. + os.makedirs(upperdirs) + + if tarinfo.islnk() or tarinfo.issym(): + self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname)) + else: + self._dbg(1, tarinfo.name) + + if tarinfo.isreg(): + self.makefile(tarinfo, targetpath) + elif tarinfo.isdir(): + self.makedir(tarinfo, targetpath) + elif tarinfo.isfifo(): + self.makefifo(tarinfo, targetpath) + elif tarinfo.ischr() or tarinfo.isblk(): + self.makedev(tarinfo, targetpath) + elif tarinfo.islnk() or tarinfo.issym(): + self.makelink(tarinfo, targetpath) + elif tarinfo.type not in SUPPORTED_TYPES: + self.makeunknown(tarinfo, targetpath) + else: + self.makefile(tarinfo, targetpath) + + if set_attrs: + self.chown(tarinfo, targetpath) + if not tarinfo.issym(): + self.chmod(tarinfo, targetpath) + self.utime(tarinfo, targetpath) + + #-------------------------------------------------------------------------- + # Below are the different file methods. They are called via + # _extract_member() when extract() is called. They can be replaced in a + # subclass to implement other functionality. + + def makedir(self, tarinfo, targetpath): + """Make a directory called targetpath. + """ + try: + # Use a safe mode for the directory, the real mode is set + # later in _extract_member(). + os.mkdir(targetpath, 0o700) + except EnvironmentError as e: + if e.errno != errno.EEXIST: + raise + + def makefile(self, tarinfo, targetpath): + """Make a file called targetpath. + """ + source = self.fileobj + source.seek(tarinfo.offset_data) + target = bltn_open(targetpath, "wb") + if tarinfo.sparse is not None: + for offset, size in tarinfo.sparse: + target.seek(offset) + copyfileobj(source, target, size) + else: + copyfileobj(source, target, tarinfo.size) + target.seek(tarinfo.size) + target.truncate() + target.close() + + def makeunknown(self, tarinfo, targetpath): + """Make a file from a TarInfo object with an unknown type + at targetpath. + """ + self.makefile(tarinfo, targetpath) + self._dbg(1, "tarfile: Unknown file type %r, " \ + "extracted as regular file." % tarinfo.type) + + def makefifo(self, tarinfo, targetpath): + """Make a fifo called targetpath. + """ + if hasattr(os, "mkfifo"): + os.mkfifo(targetpath) + else: + raise ExtractError("fifo not supported by system") + + def makedev(self, tarinfo, targetpath): + """Make a character or block device called targetpath. + """ + if not hasattr(os, "mknod") or not hasattr(os, "makedev"): + raise ExtractError("special devices not supported by system") + + mode = tarinfo.mode + if tarinfo.isblk(): + mode |= stat.S_IFBLK + else: + mode |= stat.S_IFCHR + + os.mknod(targetpath, mode, + os.makedev(tarinfo.devmajor, tarinfo.devminor)) + + def makelink(self, tarinfo, targetpath): + """Make a (symbolic) link called targetpath. If it cannot be created + (platform limitation), we try to make a copy of the referenced file + instead of a link. + """ + try: + # For systems that support symbolic and hard links. + if tarinfo.issym(): + os.symlink(tarinfo.linkname, targetpath) + else: + # See extract(). + if os.path.exists(tarinfo._link_target): + os.link(tarinfo._link_target, targetpath) + else: + self._extract_member(self._find_link_target(tarinfo), + targetpath) + except symlink_exception: + if tarinfo.issym(): + linkpath = os.path.join(os.path.dirname(tarinfo.name), + tarinfo.linkname) + else: + linkpath = tarinfo.linkname + else: + try: + self._extract_member(self._find_link_target(tarinfo), + targetpath) + except KeyError: + raise ExtractError("unable to resolve link inside archive") + + def chown(self, tarinfo, targetpath): + """Set owner of targetpath according to tarinfo. + """ + if pwd and hasattr(os, "geteuid") and os.geteuid() == 0: + # We have to be root to do so. + try: + g = grp.getgrnam(tarinfo.gname)[2] + except KeyError: + g = tarinfo.gid + try: + u = pwd.getpwnam(tarinfo.uname)[2] + except KeyError: + u = tarinfo.uid + try: + if tarinfo.issym() and hasattr(os, "lchown"): + os.lchown(targetpath, u, g) + else: + if sys.platform != "os2emx": + os.chown(targetpath, u, g) + except EnvironmentError as e: + raise ExtractError("could not change owner") + + def chmod(self, tarinfo, targetpath): + """Set file permissions of targetpath according to tarinfo. + """ + if hasattr(os, 'chmod'): + try: + os.chmod(targetpath, tarinfo.mode) + except EnvironmentError as e: + raise ExtractError("could not change mode") + + def utime(self, tarinfo, targetpath): + """Set modification time of targetpath according to tarinfo. + """ + if not hasattr(os, 'utime'): + return + try: + os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime)) + except EnvironmentError as e: + raise ExtractError("could not change modification time") + + #-------------------------------------------------------------------------- + def next(self): + """Return the next member of the archive as a TarInfo object, when + TarFile is opened for reading. Return None if there is no more + available. + """ + self._check("ra") + if self.firstmember is not None: + m = self.firstmember + self.firstmember = None + return m + + # Read the next block. + self.fileobj.seek(self.offset) + tarinfo = None + while True: + try: + tarinfo = self.tarinfo.fromtarfile(self) + except EOFHeaderError as e: + if self.ignore_zeros: + self._dbg(2, "0x%X: %s" % (self.offset, e)) + self.offset += BLOCKSIZE + continue + except InvalidHeaderError as e: + if self.ignore_zeros: + self._dbg(2, "0x%X: %s" % (self.offset, e)) + self.offset += BLOCKSIZE + continue + elif self.offset == 0: + raise ReadError(str(e)) + except EmptyHeaderError: + if self.offset == 0: + raise ReadError("empty file") + except TruncatedHeaderError as e: + if self.offset == 0: + raise ReadError(str(e)) + except SubsequentHeaderError as e: + raise ReadError(str(e)) + break + + if tarinfo is not None: + self.members.append(tarinfo) + else: + self._loaded = True + + return tarinfo + + #-------------------------------------------------------------------------- + # Little helper methods: + + def _getmember(self, name, tarinfo=None, normalize=False): + """Find an archive member by name from bottom to top. + If tarinfo is given, it is used as the starting point. + """ + # Ensure that all members have been loaded. + members = self.getmembers() + + # Limit the member search list up to tarinfo. + if tarinfo is not None: + members = members[:members.index(tarinfo)] + + if normalize: + name = os.path.normpath(name) + + for member in reversed(members): + if normalize: + member_name = os.path.normpath(member.name) + else: + member_name = member.name + + if name == member_name: + return member + + def _load(self): + """Read through the entire archive file and look for readable + members. + """ + while True: + tarinfo = self.next() + if tarinfo is None: + break + self._loaded = True + + def _check(self, mode=None): + """Check if TarFile is still open, and if the operation's mode + corresponds to TarFile's mode. + """ + if self.closed: + raise IOError("%s is closed" % self.__class__.__name__) + if mode is not None and self.mode not in mode: + raise IOError("bad operation for mode %r" % self.mode) + + def _find_link_target(self, tarinfo): + """Find the target member of a symlink or hardlink member in the + archive. + """ + if tarinfo.issym(): + # Always search the entire archive. + linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname + limit = None + else: + # Search the archive before the link, because a hard link is + # just a reference to an already archived file. + linkname = tarinfo.linkname + limit = tarinfo + + member = self._getmember(linkname, tarinfo=limit, normalize=True) + if member is None: + raise KeyError("linkname %r not found" % linkname) + return member + + def __iter__(self): + """Provide an iterator object. + """ + if self._loaded: + return iter(self.members) + else: + return TarIter(self) + + def _dbg(self, level, msg): + """Write debugging output to sys.stderr. + """ + if level <= self.debug: + print(msg, file=sys.stderr) + + def __enter__(self): + self._check() + return self + + def __exit__(self, type, value, traceback): + if type is None: + self.close() + else: + # An exception occurred. We must not call close() because + # it would try to write end-of-archive blocks and padding. + if not self._extfileobj: + self.fileobj.close() + self.closed = True +# class TarFile + +class TarIter(object): + """Iterator Class. + + for tarinfo in TarFile(...): + suite... + """ + + def __init__(self, tarfile): + """Construct a TarIter object. + """ + self.tarfile = tarfile + self.index = 0 + def __iter__(self): + """Return iterator object. + """ + return self + + def __next__(self): + """Return the next item using TarFile's next() method. + When all members have been read, set TarFile as _loaded. + """ + # Fix for SF #1100429: Under rare circumstances it can + # happen that getmembers() is called during iteration, + # which will cause TarIter to stop prematurely. + if not self.tarfile._loaded: + tarinfo = self.tarfile.next() + if not tarinfo: + self.tarfile._loaded = True + raise StopIteration + else: + try: + tarinfo = self.tarfile.members[self.index] + except IndexError: + raise StopIteration + self.index += 1 + return tarinfo + + next = __next__ # for Python 2.x + +#-------------------- +# exported functions +#-------------------- +def is_tarfile(name): + """Return True if name points to a tar archive that we + are able to handle, else return False. + """ + try: + t = open(name) + t.close() + return True + except TarError: + return False + +bltn_open = open +open = TarFile.open diff --git a/src/build_utils/distlib/compat.py b/src/build_utils/distlib/compat.py new file mode 100644 index 0000000000..36365b0d68 --- /dev/null +++ b/src/build_utils/distlib/compat.py @@ -0,0 +1,1102 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import absolute_import + +import os +import re +import sys + +if sys.version_info[0] < 3: + from StringIO import StringIO + string_types = basestring, + text_type = unicode + from types import FileType as file_type + import __builtin__ as builtins + import ConfigParser as configparser + from ._backport import shutil + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit + from urllib import (urlretrieve, quote as _quote, unquote, url2pathname, + pathname2url, ContentTooShortError, splittype) + + def quote(s): + if isinstance(s, unicode): + s = s.encode('utf-8') + return _quote(s) + + import urllib2 + from urllib2 import (Request, urlopen, URLError, HTTPError, + HTTPBasicAuthHandler, HTTPPasswordMgr, + HTTPSHandler, HTTPHandler, HTTPRedirectHandler, + build_opener) + import httplib + import xmlrpclib + import Queue as queue + from HTMLParser import HTMLParser + import htmlentitydefs + raw_input = raw_input + from itertools import ifilter as filter + from itertools import ifilterfalse as filterfalse + + _userprog = None + def splituser(host): + """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + global _userprog + if _userprog is None: + import re + _userprog = re.compile('^(.*)@(.*)$') + + match = _userprog.match(host) + if match: return match.group(1, 2) + return None, host + +else: + from io import StringIO + string_types = str, + text_type = str + from io import TextIOWrapper as file_type + import builtins + import configparser + import shutil + from urllib.parse import (urlparse, urlunparse, urljoin, splituser, quote, + unquote, urlsplit, urlunsplit, splittype) + from urllib.request import (urlopen, urlretrieve, Request, url2pathname, + pathname2url, + HTTPBasicAuthHandler, HTTPPasswordMgr, + HTTPSHandler, HTTPHandler, HTTPRedirectHandler, + build_opener) + from urllib.error import HTTPError, URLError, ContentTooShortError + import http.client as httplib + import urllib.request as urllib2 + import xmlrpc.client as xmlrpclib + import queue + from html.parser import HTMLParser + import html.entities as htmlentitydefs + raw_input = input + from itertools import filterfalse + filter = filter + +try: + from ssl import match_hostname, CertificateError +except ImportError: + class CertificateError(ValueError): + pass + + + def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + parts = dn.split('.') + leftmost, remainder = parts[0], parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survery of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + +try: + from types import SimpleNamespace as Container +except ImportError: + class Container(object): + """ + A generic container for when multiple values need to be returned + """ + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +try: + from shutil import which +except ImportError: + # Implementation from Python 3.3 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +# ZipFile is a context manager in 2.7, but not in 2.6 + +from zipfile import ZipFile as BaseZipFile + +if hasattr(BaseZipFile, '__enter__'): + ZipFile = BaseZipFile +else: + from zipfile import ZipExtFile as BaseZipExtFile + + class ZipExtFile(BaseZipExtFile): + def __init__(self, base): + self.__dict__.update(base.__dict__) + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + class ZipFile(BaseZipFile): + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + def open(self, *args, **kwargs): + base = BaseZipFile.open(self, *args, **kwargs) + return ZipExtFile(base) + +try: + from platform import python_implementation +except ImportError: # pragma: no cover + def python_implementation(): + """Return a string identifying the Python implementation.""" + if 'PyPy' in sys.version: + return 'PyPy' + if os.name == 'java': + return 'Jython' + if sys.version.startswith('IronPython'): + return 'IronPython' + return 'CPython' + +try: + import sysconfig +except ImportError: # pragma: no cover + from ._backport import sysconfig + +try: + callable = callable +except NameError: # pragma: no cover + from collections import Callable + + def callable(obj): + return isinstance(obj, Callable) + + +try: + fsencode = os.fsencode + fsdecode = os.fsdecode +except AttributeError: # pragma: no cover + _fsencoding = sys.getfilesystemencoding() + if _fsencoding == 'mbcs': + _fserrors = 'strict' + else: + _fserrors = 'surrogateescape' + + def fsencode(filename): + if isinstance(filename, bytes): + return filename + elif isinstance(filename, text_type): + return filename.encode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + + def fsdecode(filename): + if isinstance(filename, text_type): + return filename + elif isinstance(filename, bytes): + return filename.decode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + +try: + from tokenize import detect_encoding +except ImportError: # pragma: no cover + from codecs import BOM_UTF8, lookup + import re + + cookie_re = re.compile("coding[:=]\s*([-\w.]+)") + + def _get_normal_name(orig_enc): + """Imitates get_normal_name in tokenizer.c.""" + # Only care about the first 12 characters. + enc = orig_enc[:12].lower().replace("_", "-") + if enc == "utf-8" or enc.startswith("utf-8-"): + return "utf-8" + if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ + enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): + return "iso-8859-1" + return orig_enc + + def detect_encoding(readline): + """ + The detect_encoding() function is used to detect the encoding that should + be used to decode a Python source file. It requires one argment, readline, + in the same way as the tokenize() generator. + + It will call readline a maximum of twice, and return the encoding used + (as a string) and a list of any lines (left as bytes) it has read in. + + It detects the encoding from the presence of a utf-8 bom or an encoding + cookie as specified in pep-0263. If both a bom and a cookie are present, + but disagree, a SyntaxError will be raised. If the encoding cookie is an + invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, + 'utf-8-sig' is returned. + + If no encoding is specified, then the default of 'utf-8' will be returned. + """ + try: + filename = readline.__self__.name + except AttributeError: + filename = None + bom_found = False + encoding = None + default = 'utf-8' + def read_or_stop(): + try: + return readline() + except StopIteration: + return b'' + + def find_cookie(line): + try: + # Decode as UTF-8. Either the line is an encoding declaration, + # in which case it should be pure ASCII, or it must be UTF-8 + # per default encoding. + line_string = line.decode('utf-8') + except UnicodeDecodeError: + msg = "invalid or missing encoding declaration" + if filename is not None: + msg = '{} for {!r}'.format(msg, filename) + raise SyntaxError(msg) + + matches = cookie_re.findall(line_string) + if not matches: + return None + encoding = _get_normal_name(matches[0]) + try: + codec = lookup(encoding) + except LookupError: + # This behaviour mimics the Python interpreter + if filename is None: + msg = "unknown encoding: " + encoding + else: + msg = "unknown encoding for {!r}: {}".format(filename, + encoding) + raise SyntaxError(msg) + + if bom_found: + if codec.name != 'utf-8': + # This behaviour mimics the Python interpreter + if filename is None: + msg = 'encoding problem: utf-8' + else: + msg = 'encoding problem for {!r}: utf-8'.format(filename) + raise SyntaxError(msg) + encoding += '-sig' + return encoding + + first = read_or_stop() + if first.startswith(BOM_UTF8): + bom_found = True + first = first[3:] + default = 'utf-8-sig' + if not first: + return default, [] + + encoding = find_cookie(first) + if encoding: + return encoding, [first] + + second = read_or_stop() + if not second: + return default, [first] + + encoding = find_cookie(second) + if encoding: + return encoding, [first, second] + + return default, [first, second] + +# For converting & <-> & etc. +try: + from html import escape +except ImportError: + from cgi import escape +if sys.version_info[:2] < (3, 4): + unescape = HTMLParser().unescape +else: + from html import unescape + +try: + from collections import ChainMap +except ImportError: # pragma: no cover + from collections import MutableMapping + + try: + from reprlib import recursive_repr as _recursive_repr + except ImportError: + def _recursive_repr(fillvalue='...'): + ''' + Decorator to make a repr function return fillvalue for a recursive + call + ''' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) + return wrapper + + return decorating_function + + class ChainMap(MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + accessed or updated using the *maps* attribute. There is no other state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self): # like Django's Context.push() + 'New ChainMap with a new dict followed by all previous maps.' + return self.__class__({}, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + +try: + from imp import cache_from_source +except ImportError: # pragma: no cover + def cache_from_source(path, debug_override=None): + assert path.endswith('.py') + if debug_override is None: + debug_override = __debug__ + if debug_override: + suffix = 'c' + else: + suffix = 'o' + return path + suffix + +try: + from collections import OrderedDict +except ImportError: # pragma: no cover +## {{{ http://code.activestate.com/recipes/576693/ (r9) +# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. +# Passes Python2.7's test suite and incorporates all the latest updates. + try: + from thread import get_ident as _get_ident + except ImportError: + from dummy_thread import get_ident as _get_ident + + try: + from _abcoll import KeysView, ValuesView, ItemsView + except ImportError: + pass + + + class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as for regular dictionaries. + + # The internal self.__map dictionary maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # Each link is stored as a list of length three: [PREV, NEXT, KEY]. + + def __init__(self, *args, **kwds): + '''Initialize an ordered dictionary. Signature is the same as for + regular dictionaries, but keyword arguments are not recommended + because their insertion order is arbitrary. + + ''' + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__root + except AttributeError: + self.__root = root = [] # sentinel node + root[:] = [root, root, None] + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link which goes at the end of the linked + # list, and the inherited dictionary is updated with the new key/value pair. + if key not in self: + root = self.__root + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which is + # then removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link_prev, link_next, key = self.__map.pop(key) + link_prev[1] = link_next + link_next[0] = link_prev + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + root = self.__root + curr = root[0] + while curr is not root: + yield curr[2] + curr = curr[0] + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + try: + for node in self.__map.itervalues(): + del node[:] + root = self.__root + root[:] = [root, root, None] + self.__map.clear() + except AttributeError: + pass + dict.clear(self) + + def popitem(self, last=True): + '''od.popitem() -> (k, v), return and remove a (key, value) pair. + Pairs are returned in LIFO order if last is true or FIFO order if false. + + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root[0] + link_prev = link[0] + link_prev[1] = root + root[0] = link_prev + else: + link = root[1] + link_next = link[1] + root[1] = link_next + link_next[0] = root + key = link[2] + del self.__map[key] + value = dict.pop(self, key) + return key, value + + # -- the following methods do not depend on the internal structure -- + + def keys(self): + 'od.keys() -> list of keys in od' + return list(self) + + def values(self): + 'od.values() -> list of values in od' + return [self[key] for key in self] + + def items(self): + 'od.items() -> list of (key, value) pairs in od' + return [(key, self[key]) for key in self] + + def iterkeys(self): + 'od.iterkeys() -> an iterator over the keys in od' + return iter(self) + + def itervalues(self): + 'od.itervalues -> an iterator over the values in od' + for k in self: + yield self[k] + + def iteritems(self): + 'od.iteritems -> an iterator over the (key, value) items in od' + for k in self: + yield (k, self[k]) + + def update(*args, **kwds): + '''od.update(E, **F) -> None. Update od from dict/iterable E and F. + + If E is a dict instance, does: for k in E: od[k] = E[k] + If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] + Or if E is an iterable of items, does: for k, v in E: od[k] = v + In either case, this is followed by: for k, v in F.items(): od[k] = v + + ''' + if len(args) > 2: + raise TypeError('update() takes at most 2 positional ' + 'arguments (%d given)' % (len(args),)) + elif not args: + raise TypeError('update() takes at least 1 argument (0 given)') + self = args[0] + # Make progressively weaker assumptions about "other" + other = () + if len(args) == 2: + other = args[1] + if isinstance(other, dict): + for key in other: + self[key] = other[key] + elif hasattr(other, 'keys'): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + __update = update # let subclasses override update without breaking __init__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' + if key in self: + return self[key] + self[key] = default + return default + + def __repr__(self, _repr_running=None): + 'od.__repr__() <==> repr(od)' + if not _repr_running: _repr_running = {} + call_key = id(self), _get_ident() + if call_key in _repr_running: + return '...' + _repr_running[call_key] = 1 + try: + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + finally: + del _repr_running[call_key] + + def __reduce__(self): + 'Return state information for pickling' + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S + and values equal to v (which defaults to None). + + ''' + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + # -- the following methods are only used in Python 2.7 -- + + def viewkeys(self): + "od.viewkeys() -> a set-like object providing a view on od's keys" + return KeysView(self) + + def viewvalues(self): + "od.viewvalues() -> an object providing a view on od's values" + return ValuesView(self) + + def viewitems(self): + "od.viewitems() -> a set-like object providing a view on od's items" + return ItemsView(self) + +try: + from logging.config import BaseConfigurator, valid_ident +except ImportError: # pragma: no cover + IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) + + + def valid_ident(s): + m = IDENTIFIER.match(s) + if not m: + raise ValueError('Not a valid Python identifier: %r' % s) + return True + + + # The ConvertingXXX classes are wrappers around standard Python containers, + # and they serve to convert any suitable values in the container. The + # conversion converts base dicts, lists and tuples to their wrapped + # equivalents, whereas strings which match a conversion format are converted + # appropriately. + # + # Each wrapper should have a configurator attribute holding the actual + # configurator to use for conversion. + + class ConvertingDict(dict): + """A converting dictionary wrapper.""" + + def __getitem__(self, key): + value = dict.__getitem__(self, key) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def get(self, key, default=None): + value = dict.get(self, key, default) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, key, default=None): + value = dict.pop(self, key, default) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class ConvertingList(list): + """A converting list wrapper.""" + def __getitem__(self, key): + value = list.__getitem__(self, key) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, idx=-1): + value = list.pop(self, idx) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + return result + + class ConvertingTuple(tuple): + """A converting tuple wrapper.""" + def __getitem__(self, key): + value = tuple.__getitem__(self, key) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class BaseConfigurator(object): + """ + The configurator base class which defines some useful defaults. + """ + + CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') + + WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') + DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') + INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') + DIGIT_PATTERN = re.compile(r'^\d+$') + + value_converters = { + 'ext' : 'ext_convert', + 'cfg' : 'cfg_convert', + } + + # We might want to use a different one, e.g. importlib + importer = staticmethod(__import__) + + def __init__(self, config): + self.config = ConvertingDict(config) + self.config.configurator = self + + def resolve(self, s): + """ + Resolve strings to objects using standard import and attribute + syntax. + """ + name = s.split('.') + used = name.pop(0) + try: + found = self.importer(used) + for frag in name: + used += '.' + frag + try: + found = getattr(found, frag) + except AttributeError: + self.importer(used) + found = getattr(found, frag) + return found + except ImportError: + e, tb = sys.exc_info()[1:] + v = ValueError('Cannot resolve %r: %s' % (s, e)) + v.__cause__, v.__traceback__ = e, tb + raise v + + def ext_convert(self, value): + """Default converter for the ext:// protocol.""" + return self.resolve(value) + + def cfg_convert(self, value): + """Default converter for the cfg:// protocol.""" + rest = value + m = self.WORD_PATTERN.match(rest) + if m is None: + raise ValueError("Unable to convert %r" % value) + else: + rest = rest[m.end():] + d = self.config[m.groups()[0]] + #print d, rest + while rest: + m = self.DOT_PATTERN.match(rest) + if m: + d = d[m.groups()[0]] + else: + m = self.INDEX_PATTERN.match(rest) + if m: + idx = m.groups()[0] + if not self.DIGIT_PATTERN.match(idx): + d = d[idx] + else: + try: + n = int(idx) # try as number first (most likely) + d = d[n] + except TypeError: + d = d[idx] + if m: + rest = rest[m.end():] + else: + raise ValueError('Unable to convert ' + '%r at %r' % (value, rest)) + #rest should be empty + return d + + def convert(self, value): + """ + Convert values to an appropriate type. dicts, lists and tuples are + replaced by their converting alternatives. Strings are checked to + see if they have a conversion format and are converted if they do. + """ + if not isinstance(value, ConvertingDict) and isinstance(value, dict): + value = ConvertingDict(value) + value.configurator = self + elif not isinstance(value, ConvertingList) and isinstance(value, list): + value = ConvertingList(value) + value.configurator = self + elif not isinstance(value, ConvertingTuple) and\ + isinstance(value, tuple): + value = ConvertingTuple(value) + value.configurator = self + elif isinstance(value, string_types): + m = self.CONVERT_PATTERN.match(value) + if m: + d = m.groupdict() + prefix = d['prefix'] + converter = self.value_converters.get(prefix, None) + if converter: + suffix = d['suffix'] + converter = getattr(self, converter) + value = converter(suffix) + return value + + def configure_custom(self, config): + """Configure an object with a user-supplied factory.""" + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) + result = c(**kwargs) + if props: + for name, value in props.items(): + setattr(result, name, value) + return result + + def as_tuple(self, value): + """Utility function which converts lists to tuples.""" + if isinstance(value, list): + value = tuple(value) + return value diff --git a/src/build_utils/distlib/database.py b/src/build_utils/distlib/database.py new file mode 100644 index 0000000000..9358d08f46 --- /dev/null +++ b/src/build_utils/distlib/database.py @@ -0,0 +1,1301 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2013 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""PEP 376 implementation.""" + +from __future__ import unicode_literals + +import base64 +import codecs +import contextlib +import hashlib +import logging +import os +import posixpath +import sys +import zipimport + +from . import DistlibException, resources +from .compat import StringIO +from .version import get_scheme, UnsupportedVersionError +from .metadata import Metadata, METADATA_FILENAME +from .util import (parse_requirement, cached_property, parse_name_and_version, + read_exports, write_exports, CSVReader, CSVWriter) + + +__all__ = ['Distribution', 'BaseInstalledDistribution', + 'InstalledDistribution', 'EggInfoDistribution', + 'DistributionPath'] + + +logger = logging.getLogger(__name__) + +EXPORTS_FILENAME = 'pydist-exports.json' +COMMANDS_FILENAME = 'pydist-commands.json' + +DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', + 'RESOURCES', EXPORTS_FILENAME, 'SHARED') + +DISTINFO_EXT = '.dist-info' + + +class _Cache(object): + """ + A simple cache mapping names and .dist-info paths to distributions + """ + def __init__(self): + """ + Initialise an instance. There is normally one for each DistributionPath. + """ + self.name = {} + self.path = {} + self.generated = False + + def clear(self): + """ + Clear the cache, setting it to its initial state. + """ + self.name.clear() + self.path.clear() + self.generated = False + + def add(self, dist): + """ + Add a distribution to the cache. + :param dist: The distribution to add. + """ + if dist.path not in self.path: + self.path[dist.path] = dist + self.name.setdefault(dist.key, []).append(dist) + + +class DistributionPath(object): + """ + Represents a set of distributions installed on a path (typically sys.path). + """ + def __init__(self, path=None, include_egg=False): + """ + Create an instance from a path, optionally including legacy (distutils/ + setuptools/distribute) distributions. + :param path: The path to use, as a list of directories. If not specified, + sys.path is used. + :param include_egg: If True, this instance will look for and return legacy + distributions as well as those based on PEP 376. + """ + if path is None: + path = sys.path + self.path = path + self._include_dist = True + self._include_egg = include_egg + + self._cache = _Cache() + self._cache_egg = _Cache() + self._cache_enabled = True + self._scheme = get_scheme('default') + + def _get_cache_enabled(self): + return self._cache_enabled + + def _set_cache_enabled(self, value): + self._cache_enabled = value + + cache_enabled = property(_get_cache_enabled, _set_cache_enabled) + + def clear_cache(self): + """ + Clears the internal cache. + """ + self._cache.clear() + self._cache_egg.clear() + + + def _yield_distributions(self): + """ + Yield .dist-info and/or .egg(-info) distributions. + """ + # We need to check if we've seen some resources already, because on + # some Linux systems (e.g. some Debian/Ubuntu variants) there are + # symlinks which alias other files in the environment. + seen = set() + for path in self.path: + finder = resources.finder_for_path(path) + if finder is None: + continue + r = finder.find('') + if not r or not r.is_container: + continue + rset = sorted(r.resources) + for entry in rset: + r = finder.find(entry) + if not r or r.path in seen: + continue + if self._include_dist and entry.endswith(DISTINFO_EXT): + metadata_path = posixpath.join(entry, METADATA_FILENAME) + pydist = finder.find(metadata_path) + if not pydist: + continue + + metadata = Metadata(fileobj=pydist.as_stream(), + scheme='legacy') + logger.debug('Found %s', r.path) + seen.add(r.path) + yield new_dist_class(r.path, metadata=metadata, + env=self) + elif self._include_egg and entry.endswith(('.egg-info', + '.egg')): + logger.debug('Found %s', r.path) + seen.add(r.path) + yield old_dist_class(r.path, self) + + def _generate_cache(self): + """ + Scan the path for distributions and populate the cache with + those that are found. + """ + gen_dist = not self._cache.generated + gen_egg = self._include_egg and not self._cache_egg.generated + if gen_dist or gen_egg: + for dist in self._yield_distributions(): + if isinstance(dist, InstalledDistribution): + self._cache.add(dist) + else: + self._cache_egg.add(dist) + + if gen_dist: + self._cache.generated = True + if gen_egg: + self._cache_egg.generated = True + + @classmethod + def distinfo_dirname(cls, 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""" + name = name.replace('-', '_') + return '-'.join([name, version]) + DISTINFO_EXT + + def get_distributions(self): + """ + Provides an iterator that looks for distributions and returns + :class:`InstalledDistribution` or + :class:`EggInfoDistribution` instances for each one of them. + + :rtype: iterator of :class:`InstalledDistribution` and + :class:`EggInfoDistribution` instances + """ + if not self._cache_enabled: + for dist in self._yield_distributions(): + yield dist + else: + self._generate_cache() + + for dist in self._cache.path.values(): + yield dist + + if self._include_egg: + for dist in self._cache_egg.path.values(): + yield dist + + def get_distribution(self, name): + """ + Looks for a named distribution on the path. + + This function only returns the first result found, as no more than one + value is expected. If nothing is found, ``None`` is returned. + + :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution` + or ``None`` + """ + result = None + name = name.lower() + if not self._cache_enabled: + for dist in self._yield_distributions(): + if dist.key == name: + result = dist + break + else: + self._generate_cache() + + if name in self._cache.name: + result = self._cache.name[name][0] + elif self._include_egg and name in self._cache_egg.name: + result = self._cache_egg.name[name][0] + return result + + def provides_distribution(self, name, version=None): + """ + Iterates over all distributions to find which distributions provide *name*. + If a *version* is provided, it will be used to filter the results. + + 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 + """ + matcher = None + if not version is None: + try: + matcher = self._scheme.matcher('%s (%s)' % (name, version)) + except ValueError: + raise DistlibException('invalid name or version: %r, %r' % + (name, version)) + + for dist in self.get_distributions(): + provided = dist.provides + + for p in provided: + p_name, p_ver = parse_name_and_version(p) + if matcher is None: + if p_name == name: + yield dist + break + else: + if p_name == name and matcher.match(p_ver): + yield dist + break + + def get_file_path(self, name, relative_path): + """ + Return the path to a resource file. + """ + dist = self.get_distribution(name) + if dist is None: + raise LookupError('no distribution named %r found' % name) + return dist.get_resource_path(relative_path) + + def get_exported_entries(self, category, name=None): + """ + Return all of the exported entries in a particular category. + + :param category: The category to search for entries. + :param name: If specified, only entries with that name are returned. + """ + for dist in self.get_distributions(): + r = dist.exports + if category in r: + d = r[category] + if name is not None: + if name in d: + yield d[name] + else: + for v in d.values(): + yield v + + +class Distribution(object): + """ + A base class for distributions, whether installed or from indexes. + Either way, it must have some metadata, so that's all that's needed + for construction. + """ + + build_time_dependency = False + """ + Set to True if it's known to be only a build-time dependency (i.e. + not needed after installation). + """ + + 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, metadata): + """ + Initialise an instance. + :param metadata: The instance of :class:`Metadata` describing this + distribution. + """ + self.metadata = metadata + self.name = metadata.name + self.key = self.name.lower() # for case-insensitive comparisons + self.version = metadata.version + self.locator = None + self.digest = None + self.extras = None # additional features requested + self.context = None # environment marker overrides + + @property + def source_url(self): + """ + The source archive download URL for this distribution. + """ + return self.metadata.source_url + + download_url = source_url # Backward compatibility + + @property + def name_and_version(self): + """ + A utility property which displays the name and version in parentheses. + """ + return '%s (%s)' % (self.name, self.version) + + @property + def provides(self): + """ + A set of distribution names and versions provided by this distribution. + :return: A set of "name (version)" strings. + """ + plist = self.metadata.provides + s = '%s (%s)' % (self.name, self.version) + if s not in plist: + plist.append(s) + return plist + + def _get_requirements(self, req_attr): + reqts = getattr(self.metadata, req_attr) + return set(self.metadata.get_requirements(reqts, extras=self.extras, + env=self.context)) + + @property + def run_requires(self): + return self._get_requirements('run_requires') + + @property + def meta_requires(self): + return self._get_requirements('meta_requires') + + @property + def build_requires(self): + return self._get_requirements('build_requires') + + @property + def test_requires(self): + return self._get_requirements('test_requires') + + @property + def dev_requires(self): + return self._get_requirements('dev_requires') + + def matches_requirement(self, req): + """ + Say if this instance matches (fulfills) a requirement. + :param req: The requirement to match. + :rtype req: str + :return: True if it matches, else False. + """ + # Requirement may contain extras - parse to lose those + # from what's passed to the matcher + r = parse_requirement(req) + scheme = get_scheme(self.metadata.scheme) + try: + matcher = scheme.matcher(r.requirement) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', + req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + result = False + for p in self.provides: + p_name, p_ver = parse_name_and_version(p) + if p_name != name: + continue + try: + result = matcher.match(p_ver) + break + except UnsupportedVersionError: + pass + return result + + def __repr__(self): + """ + Return a textual representation of this instance, + """ + if self.source_url: + suffix = ' [%s]' % self.source_url + else: + suffix = '' + return '' % (self.name, self.version, suffix) + + def __eq__(self, other): + """ + See if this distribution is the same as another. + :param other: The distribution to compare with. To be equal to one + another. distributions must have the same type, name, + version and source_url. + :return: True if it is the same, else False. + """ + if type(other) is not type(self): + result = False + else: + result = (self.name == other.name and + self.version == other.version and + self.source_url == other.source_url) + return result + + def __hash__(self): + """ + Compute hash in a way which matches the equality test. + """ + return hash(self.name) + hash(self.version) + hash(self.source_url) + + +class BaseInstalledDistribution(Distribution): + """ + This is the base class for installed distributions (whether PEP 376 or + legacy). + """ + + hasher = None + + def __init__(self, metadata, path, env=None): + """ + Initialise an instance. + :param metadata: An instance of :class:`Metadata` which describes the + distribution. This will normally have been initialised + from a metadata file in the ``path``. + :param path: The path of the ``.dist-info`` or ``.egg-info`` + directory for the distribution. + :param env: This is normally the :class:`DistributionPath` + instance where this distribution was found. + """ + super(BaseInstalledDistribution, self).__init__(metadata) + self.path = path + self.dist_path = env + + def get_hash(self, data, hasher=None): + """ + Get the hash of some data, using a particular hash algorithm, if + specified. + + :param data: The data to be hashed. + :type data: bytes + :param hasher: The name of a hash implementation, supported by hashlib, + or ``None``. Examples of valid values are ``'sha1'``, + ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and + ``'sha512'``. If no hasher is specified, the ``hasher`` + attribute of the :class:`InstalledDistribution` instance + is used. If the hasher is determined to be ``None``, MD5 + is used as the hashing algorithm. + :returns: The hash of the data. If a hasher was explicitly specified, + the returned hash will be prefixed with the specified hasher + followed by '='. + :rtype: str + """ + if hasher is None: + hasher = self.hasher + if hasher is None: + hasher = hashlib.md5 + prefix = '' + else: + hasher = getattr(hashlib, hasher) + prefix = '%s=' % self.hasher + digest = hasher(data).digest() + digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii') + return '%s%s' % (prefix, digest) + + +class InstalledDistribution(BaseInstalledDistribution): + """ + Created with the *path* of the ``.dist-info`` directory provided to the + constructor. It reads the metadata contained in ``pydist.json`` when it is + instantiated., or uses a passed in Metadata instance (useful for when + dry-run mode is being used). + """ + + hasher = 'sha256' + + def __init__(self, path, metadata=None, env=None): + self.finder = finder = resources.finder_for_path(path) + if finder is None: + import pdb; pdb.set_trace () + if env and env._cache_enabled and path in env._cache.path: + metadata = env._cache.path[path].metadata + elif metadata is None: + r = finder.find(METADATA_FILENAME) + # Temporary - for legacy support + if r is None: + r = finder.find('METADATA') + if r is None: + raise ValueError('no %s found in %s' % (METADATA_FILENAME, + path)) + with contextlib.closing(r.as_stream()) as stream: + metadata = Metadata(fileobj=stream, scheme='legacy') + + super(InstalledDistribution, self).__init__(metadata, path, env) + + if env and env._cache_enabled: + env._cache.add(self) + + try: + r = finder.find('REQUESTED') + except AttributeError: + import pdb; pdb.set_trace () + self.requested = r is not None + + def __repr__(self): + return '' % ( + self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def _get_records(self): + """ + Get the list of installed files for the distribution + :return: A list of tuples of path, hash and size. Note that hash and + size might be ``None`` for some entries. The path is exactly + as stored in the file (which is as in PEP 376). + """ + results = [] + r = self.get_distinfo_resource('RECORD') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as record_reader: + # Base location is parent dir of .dist-info dir + #base_location = os.path.dirname(self.path) + #base_location = os.path.abspath(base_location) + for row in record_reader: + missing = [None for i in range(len(row), 3)] + path, checksum, size = row + missing + #if not os.path.isabs(path): + # path = path.replace('/', os.sep) + # path = os.path.join(base_location, path) + results.append((path, checksum, size)) + return results + + @cached_property + def exports(self): + """ + Return the information exported by this distribution. + :return: A dictionary of exports, mapping an export category to a dict + of :class:`ExportEntry` instances describing the individual + export entries, and keyed by name. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + result = self.read_exports() + return result + + def read_exports(self): + """ + Read exports data from a file in .ini format. + + :return: A dictionary of exports, mapping an export category to a list + of :class:`ExportEntry` instances describing the individual + export entries. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + with contextlib.closing(r.as_stream()) as stream: + result = read_exports(stream) + return result + + def write_exports(self, exports): + """ + Write a dictionary of exports to a file in .ini format. + :param exports: A dictionary of exports, mapping an export category to + a list of :class:`ExportEntry` instances describing the + individual export entries. + """ + rf = self.get_distinfo_file(EXPORTS_FILENAME) + with open(rf, 'w') as f: + write_exports(exports, f) + + def get_resource_path(self, relative_path): + """ + NOTE: This API may change in the future. + + Return the absolute path to a resource file with the given relative + path. + + :param relative_path: The path, relative to .dist-info, of the resource + of interest. + :return: The absolute path where the resource is to be found. + """ + r = self.get_distinfo_resource('RESOURCES') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as resources_reader: + 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): + """ + Iterates over the ``RECORD`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: iterator of (path, hash, size) + """ + for result in self._get_records(): + yield result + + def write_installed_files(self, paths, prefix, dry_run=False): + """ + Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any + existing ``RECORD`` file is silently overwritten. + + prefix is used to determine when to write absolute paths. + """ + prefix = os.path.join(prefix, '') + base = os.path.dirname(self.path) + base_under_prefix = base.startswith(prefix) + base = os.path.join(base, '') + record_path = self.get_distinfo_file('RECORD') + logger.info('creating %s', record_path) + if dry_run: + return None + with CSVWriter(record_path) as writer: + for path in paths: + if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')): + # do not put size and hash, as in PEP-376 + hash_value = size = '' + else: + size = '%d' % os.path.getsize(path) + with open(path, 'rb') as fp: + hash_value = self.get_hash(fp.read()) + if path.startswith(base) or (base_under_prefix and + path.startswith(prefix)): + path = os.path.relpath(path, base) + writer.writerow((path, hash_value, size)) + + # add the RECORD file itself + if record_path.startswith(base): + record_path = os.path.relpath(record_path, base) + writer.writerow((record_path, '', '')) + return record_path + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + base = os.path.dirname(self.path) + record_path = self.get_distinfo_file('RECORD') + for path, hash_value, size in self.list_installed_files(): + if not os.path.isabs(path): + path = os.path.join(base, path) + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + elif os.path.isfile(path): + actual_size = str(os.path.getsize(path)) + if size and actual_size != size: + mismatches.append((path, 'size', size, actual_size)) + elif hash_value: + if '=' in hash_value: + hasher = hash_value.split('=', 1)[0] + else: + hasher = None + + with open(path, 'rb') as f: + actual_hash = self.get_hash(f.read(), hasher) + if actual_hash != hash_value: + mismatches.append((path, 'hash', hash_value, actual_hash)) + return mismatches + + @cached_property + def shared_locations(self): + """ + A dictionary of shared locations whose keys are in the set 'prefix', + 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'. + The corresponding value is the absolute path of that category for + this distribution, and takes into account any paths selected by the + user at installation time (e.g. via command-line arguments). In the + case of the 'namespace' key, this would be a list of absolute paths + for the roots of namespace packages in this distribution. + + The first time this property is accessed, the relevant information is + read from the SHARED file in the .dist-info directory. + """ + result = {} + shared_path = os.path.join(self.path, 'SHARED') + if os.path.isfile(shared_path): + with codecs.open(shared_path, 'r', encoding='utf-8') as f: + lines = f.read().splitlines() + for line in lines: + key, value = line.split('=', 1) + if key == 'namespace': + result.setdefault(key, []).append(value) + else: + result[key] = value + return result + + def write_shared_locations(self, paths, dry_run=False): + """ + Write shared location information to the SHARED file in .dist-info. + :param paths: A dictionary as described in the documentation for + :meth:`shared_locations`. + :param dry_run: If True, the action is logged but no file is actually + written. + :return: The path of the file written to. + """ + shared_path = os.path.join(self.path, 'SHARED') + logger.info('creating %s', shared_path) + if dry_run: + return None + lines = [] + for key in ('prefix', 'lib', 'headers', 'scripts', 'data'): + path = paths[key] + if os.path.isdir(paths[key]): + lines.append('%s=%s' % (key, path)) + for ns in paths.get('namespace', ()): + lines.append('namespace=%s' % ns) + + with codecs.open(shared_path, 'w', encoding='utf-8') as f: + f.write('\n'.join(lines)) + return shared_path + + def get_distinfo_resource(self, path): + if path not in DIST_FILES: + raise DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + finder = resources.finder_for_path(self.path) + if finder is None: + raise DistlibException('Unable to get a finder for %s' % self.path) + return finder.find(path) + + def get_distinfo_file(self, path): + """ + Returns a path located under the ``.dist-info`` directory. Returns a + string representing the 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:`DistlibException` is raised + :type path: str + :rtype: str + """ + # 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 DistlibException( + '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 DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + + return os.path.join(self.path, path) + + def list_distinfo_files(self): + """ + 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. + + :returns: iterator of paths + """ + base = os.path.dirname(self.path) + for path, checksum, size in self._get_records(): + # XXX add separator or use real relpath algo + if not os.path.isabs(path): + path = os.path.join(base, path) + if path.startswith(self.path): + yield path + + def __eq__(self, other): + return (isinstance(other, InstalledDistribution) and + self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +class EggInfoDistribution(BaseInstalledDistribution): + """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.""" + + requested = True # as we have no way of knowing, assume it was + shared_locations = {} + + def __init__(self, path, env=None): + def set_name_and_version(s, n, v): + s.name = n + s.key = n.lower() # for case-insensitive comparisons + s.version = v + + self.path = path + self.dist_path = env + if env and env._cache_enabled and path in env._cache_egg.path: + metadata = env._cache_egg.path[path].metadata + set_name_and_version(self, metadata.name, metadata.version) + else: + metadata = self._get_metadata(path) + + # Need to be set before caching + set_name_and_version(self, metadata.name, metadata.version) + + if env and env._cache_enabled: + env._cache_egg.add(self) + super(EggInfoDistribution, self).__init__(metadata, path, env) + + def _get_metadata(self, path): + requires = None + + def parse_requires_data(data): + """Create a list of dependencies from a requires.txt file. + + *data*: the contents of a setuptools-produced requires.txt file. + """ + reqs = [] + lines = data.splitlines() + for line in lines: + line = line.strip() + if line.startswith('['): + logger.warning('Unexpected line: quitting requirement scan: %r', + line) + break + r = parse_requirement(line) + if not r: + logger.warning('Not recognised as a requirement: %r', line) + continue + if r.extras: + logger.warning('extra requirements in requires.txt are ' + 'not supported') + if not r.constraints: + reqs.append(r.name) + else: + cons = ', '.join('%s%s' % c for c in r.constraints) + reqs.append('%s (%s)' % (r.name, cons)) + return reqs + + def parse_requires_path(req_path): + """Create a list of dependencies from a requires.txt file. + + *req_path*: the path to a setuptools-produced requires.txt file. + """ + + reqs = [] + try: + with codecs.open(req_path, 'r', 'utf-8') as fp: + reqs = parse_requires_data(fp.read()) + except IOError: + pass + return reqs + + if path.endswith('.egg'): + if os.path.isdir(path): + meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + metadata = Metadata(path=meta_path, scheme='legacy') + req_path = os.path.join(path, 'EGG-INFO', 'requires.txt') + requires = parse_requires_path(req_path) + 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')) + metadata = Metadata(fileobj=fileobj, scheme='legacy') + try: + data = zipf.get_data('EGG-INFO/requires.txt') + requires = parse_requires_data(data.decode('utf-8')) + except IOError: + requires = None + elif path.endswith('.egg-info'): + if os.path.isdir(path): + req_path = os.path.join(path, 'requires.txt') + requires = parse_requires_path(req_path) + path = os.path.join(path, 'PKG-INFO') + metadata = Metadata(path=path, scheme='legacy') + else: + raise DistlibException('path must end with .egg-info or .egg, ' + 'got %r' % path) + + if requires: + metadata.add_requirements(requires) + return metadata + + def __repr__(self): + return '' % ( + self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + record_path = os.path.join(self.path, 'installed-files.txt') + if os.path.exists(record_path): + for path, _, _ in self.list_installed_files(): + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + return mismatches + + def list_installed_files(self): + """ + Iterates over the ``installed-files.txt`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: a list of (path, hash, size) + """ + + def _md5(path): + f = open(path, 'rb') + try: + content = f.read() + finally: + f.close() + return hashlib.md5(content).hexdigest() + + def _size(path): + return os.stat(path).st_size + + record_path = os.path.join(self.path, 'installed-files.txt') + result = [] + if os.path.exists(record_path): + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + p = os.path.normpath(os.path.join(self.path, line)) + # "./" is present as a marker between installed files + # and installation metadata files + if not os.path.exists(p): + logger.warning('Non-existent file: %s', p) + if p.endswith(('.pyc', '.pyo')): + continue + #otherwise fall through and fail + if not os.path.isdir(p): + result.append((p, _md5(p), _size(p))) + result.append((record_path, None, None)) + return result + + def list_distinfo_files(self, absolute=False): + """ + Iterates over the ``installed-files.txt`` entries and returns paths for + each line if the path is pointing to a file located in the + ``.egg-info`` directory or one of its subdirectories. + + :parameter absolute: If *absolute* is ``True``, each returned path is + transformed into a local absolute path. Otherwise the + raw value from ``installed-files.txt`` is returned. + :type absolute: boolean + :returns: iterator of paths + """ + record_path = os.path.join(self.path, 'installed-files.txt') + skip = True + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line == './': + skip = False + continue + if not skip: + p = os.path.normpath(os.path.join(self.path, line)) + if p.startswith(self.path): + if absolute: + yield p + else: + yield line + + 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__ + +new_dist_class = InstalledDistribution +old_dist_class = EggInfoDistribution + + +class DependencyGraph(object): + """ + 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:`distutils2.database.InstalledDistribution` + or :class:`distutils2.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:`distutils2.database.InstalledDistribution` or + :class:`distutils2.database.EggInfoDistribution` + :type y: :class:`distutils2.database.InstalledDistribution` or + :class:`distutils2.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:`distutils2.database.InstalledDistribution` + or :class:`distutils2.database.EggInfoDistribution` + :type requirement: ``str`` + """ + logger.debug('%s missing %r', distribution, requirement) + self.missing.setdefault(distribution, []).append(requirement) + + def _repr_dist(self, dist): + return '%s %s' % (dist.name, dist.version) + + def repr_node(self, dist, level=1): + """Prints only a subgraph""" + output = [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 to_dot(self, 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 self.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 topological_sort(self): + """ + Perform a topological sort of the graph. + :return: A tuple, the first element of which is a topologically sorted + list of distributions, and the second element of which is a + list of distributions that cannot be sorted because they have + circular dependencies and so form a cycle. + """ + result = [] + # Make a shallow copy of the adjacency list + alist = {} + for k, v in self.adjacency_list.items(): + alist[k] = v[:] + while True: + # See what we can remove in this run + to_remove = [] + for k, v in list(alist.items())[:]: + if not v: + to_remove.append(k) + del alist[k] + if not to_remove: + # What's left in alist (if anything) is a cycle. + break + # Remove from the adjacency list of others + for k, v in alist.items(): + alist[k] = [(d, r) for d, r in v if d not in to_remove] + logger.debug('Moving to result: %s', + ['%s (%s)' % (d.name, d.version) for d in to_remove]) + result.extend(to_remove) + return result, list(alist.keys()) + + 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 make_graph(dists, scheme='default'): + """Makes a dependency graph from the given distributions. + + :parameter dists: a list of distributions + :type dists: list of :class:`distutils2.database.InstalledDistribution` and + :class:`distutils2.database.EggInfoDistribution` instances + :rtype: a :class:`DependencyGraph` instance + """ + scheme = get_scheme(scheme) + graph = DependencyGraph() + provided = {} # maps names to lists of (version, dist) tuples + + # first, build the graph and find out what's provided + for dist in dists: + graph.add_distribution(dist) + + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + provided.setdefault(name, []).append((version, dist)) + + # now make the edges + for dist in dists: + requires = (dist.run_requires | dist.meta_requires | + dist.build_requires | dist.dev_requires) + for req in requires: + try: + matcher = scheme.matcher(req) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', + req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + matched = False + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + graph.add_edge(dist, provider, req) + matched = True + break + if not matched: + graph.add_missing(dist, req) + return graph + + +def get_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 DistlibException('given distribution %r is not a member ' + 'of the list' % dist.name) + graph = make_graph(dists) + + dep = [dist] # dependent distributions + todo = graph.reverse_list[dist] # list of nodes we should inspect + + while todo: + d = todo.pop() + dep.append(d) + for succ in graph.reverse_list[d]: + if succ not in dep: + todo.append(succ) + + dep.pop(0) # remove dist from dep, was there to prevent infinite loops + return dep + + +def get_required_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + required by *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 DistlibException('given distribution %r is not a member ' + 'of the list' % dist.name) + graph = make_graph(dists) + + req = [] # required distributions + todo = graph.adjacency_list[dist] # list of nodes we should inspect + + while todo: + d = todo.pop()[0] + req.append(d) + for pred in graph.adjacency_list[d]: + if pred not in req: + todo.append(pred) + + return req + + +def make_dist(name, version, **kwargs): + """ + A convenience method for making a dist given just a name and version. + """ + summary = kwargs.pop('summary', 'Placeholder for summary') + md = Metadata(**kwargs) + md.name = name + md.version = version + md.summary = summary or 'Plaeholder for summary' + return Distribution(md) diff --git a/src/build_utils/distlib/index.py b/src/build_utils/distlib/index.py new file mode 100644 index 0000000000..73037c97b4 --- /dev/null +++ b/src/build_utils/distlib/index.py @@ -0,0 +1,513 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import hashlib +import logging +import os +import shutil +import subprocess +import tempfile +try: + from threading import Thread +except ImportError: + from dummy_threading import Thread + +from . import DistlibException +from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, + urlparse, build_opener, string_types) +from .util import cached_property, zip_dir, ServerProxy + +logger = logging.getLogger(__name__) + +DEFAULT_INDEX = 'https://pypi.python.org/pypi' +DEFAULT_REALM = 'pypi' + +class PackageIndex(object): + """ + This class represents a package index compatible with PyPI, the Python + Package Index. + """ + + boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$' + + def __init__(self, url=None): + """ + Initialise an instance. + + :param url: The URL of the index. If not specified, the URL for PyPI is + used. + """ + self.url = url or DEFAULT_INDEX + self.read_configuration() + scheme, netloc, path, params, query, frag = urlparse(self.url) + if params or query or frag or scheme not in ('http', 'https'): + raise DistlibException('invalid repository: %s' % self.url) + self.password_handler = None + self.ssl_verifier = None + self.gpg = None + self.gpg_home = None + self.rpc_proxy = None + with open(os.devnull, 'w') as sink: + for s in ('gpg2', 'gpg'): + try: + rc = subprocess.check_call([s, '--version'], stdout=sink, + stderr=sink) + if rc == 0: + self.gpg = s + break + except OSError: + pass + + def _get_pypirc_command(self): + """ + Get the distutils command for interacting with PyPI configurations. + :return: the command. + """ + from distutils.core import Distribution + from distutils.config import PyPIRCCommand + d = Distribution() + return PyPIRCCommand(d) + + def read_configuration(self): + """ + Read the PyPI access configuration as supported by distutils, getting + PyPI to do the acutal work. This populates ``username``, ``password``, + ``realm`` and ``url`` attributes from the configuration. + """ + # get distutils to do the work + c = self._get_pypirc_command() + c.repository = self.url + cfg = c._read_pypirc() + self.username = cfg.get('username') + self.password = cfg.get('password') + self.realm = cfg.get('realm', 'pypi') + self.url = cfg.get('repository', self.url) + + def save_configuration(self): + """ + Save the PyPI access configuration. You must have set ``username`` and + ``password`` attributes before calling this method. + + Again, distutils is used to do the actual work. + """ + self.check_credentials() + # get distutils to do the work + c = self._get_pypirc_command() + c._store_pypirc(self.username, self.password) + + def check_credentials(self): + """ + Check that ``username`` and ``password`` have been set, and raise an + exception if not. + """ + if self.username is None or self.password is None: + raise DistlibException('username and password must be set') + pm = HTTPPasswordMgr() + _, netloc, _, _, _, _ = urlparse(self.url) + pm.add_password(self.realm, netloc, self.username, self.password) + self.password_handler = HTTPBasicAuthHandler(pm) + + def register(self, metadata): + """ + Register a distribution on PyPI, using the provided metadata. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the distribution to be + registered. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + metadata.validate() + d = metadata.todict() + d[':action'] = 'verify' + request = self.encode_request(d.items(), []) + response = self.send_request(request) + d[':action'] = 'submit' + request = self.encode_request(d.items(), []) + return self.send_request(request) + + def _reader(self, name, stream, outbuf): + """ + Thread runner for reading lines of from a subprocess into a buffer. + + :param name: The logical name of the stream (used for logging only). + :param stream: The stream to read from. This will typically a pipe + connected to the output stream of a subprocess. + :param outbuf: The list to append the read lines to. + """ + while True: + s = stream.readline() + if not s: + break + s = s.decode('utf-8').rstrip() + outbuf.append(s) + logger.debug('%s: %s' % (name, s)) + stream.close() + + def get_sign_command(self, filename, signer, sign_password, + keystore=None): + """ + Return a suitable command for signing a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The signing command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + if sign_password is not None: + cmd.extend(['--batch', '--passphrase-fd', '0']) + td = tempfile.mkdtemp() + sf = os.path.join(td, os.path.basename(filename) + '.asc') + cmd.extend(['--detach-sign', '--armor', '--local-user', + signer, '--output', sf, filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd, sf + + def run_command(self, cmd, input_data=None): + """ + Run a command in a child process , passing it any input data specified. + + :param cmd: The command to run. + :param input_data: If specified, this must be a byte string containing + data to be sent to the child process. + :return: A tuple consisting of the subprocess' exit code, a list of + lines read from the subprocess' ``stdout``, and a list of + lines read from the subprocess' ``stderr``. + """ + kwargs = { + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE, + } + if input_data is not None: + kwargs['stdin'] = subprocess.PIPE + stdout = [] + stderr = [] + p = subprocess.Popen(cmd, **kwargs) + # We don't use communicate() here because we may need to + # get clever with interacting with the command + t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout)) + t1.start() + t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr)) + t2.start() + if input_data is not None: + p.stdin.write(input_data) + p.stdin.close() + + p.wait() + t1.join() + t2.join() + return p.returncode, stdout, stderr + + def sign_file(self, filename, signer, sign_password, keystore=None): + """ + Sign a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The absolute pathname of the file where the signature is + stored. + """ + cmd, sig_file = self.get_sign_command(filename, signer, sign_password, + keystore) + rc, stdout, stderr = self.run_command(cmd, + sign_password.encode('utf-8')) + if rc != 0: + raise DistlibException('sign command failed with error ' + 'code %s' % rc) + return sig_file + + def upload_file(self, metadata, filename, signer=None, sign_password=None, + filetype='sdist', pyversion='source', keystore=None): + """ + Upload a release file to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the file to be uploaded. + :param filename: The pathname of the file to be uploaded. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param filetype: The type of the file being uploaded. This is the + distutils command which produced that file, e.g. + ``sdist`` or ``bdist_wheel``. + :param pyversion: The version of Python which the release relates + to. For code compatible with any Python, this would + be ``source``, otherwise it would be e.g. ``3.2``. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.exists(filename): + raise DistlibException('not found: %s' % filename) + metadata.validate() + d = metadata.todict() + sig_file = None + if signer: + if not self.gpg: + logger.warning('no signing program available - not signed') + else: + sig_file = self.sign_file(filename, signer, sign_password, + keystore) + with open(filename, 'rb') as f: + file_data = f.read() + md5_digest = hashlib.md5(file_data).hexdigest() + sha256_digest = hashlib.sha256(file_data).hexdigest() + d.update({ + ':action': 'file_upload', + 'protcol_version': '1', + 'filetype': filetype, + 'pyversion': pyversion, + 'md5_digest': md5_digest, + 'sha256_digest': sha256_digest, + }) + files = [('content', os.path.basename(filename), file_data)] + if sig_file: + with open(sig_file, 'rb') as f: + sig_data = f.read() + files.append(('gpg_signature', os.path.basename(sig_file), + sig_data)) + shutil.rmtree(os.path.dirname(sig_file)) + request = self.encode_request(d.items(), files) + return self.send_request(request) + + def upload_documentation(self, metadata, doc_dir): + """ + Upload documentation to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the documentation to be + uploaded. + :param doc_dir: The pathname of the directory which contains the + documentation. This should be the directory that + contains the ``index.html`` for the documentation. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.isdir(doc_dir): + raise DistlibException('not a directory: %r' % doc_dir) + fn = os.path.join(doc_dir, 'index.html') + if not os.path.exists(fn): + raise DistlibException('not found: %r' % fn) + metadata.validate() + name, version = metadata.name, metadata.version + zip_data = zip_dir(doc_dir).getvalue() + fields = [(':action', 'doc_upload'), + ('name', name), ('version', version)] + files = [('content', name, zip_data)] + request = self.encode_request(fields, files) + return self.send_request(request) + + def get_verify_command(self, signature_filename, data_filename, + keystore=None): + """ + Return a suitable command for verifying a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The verifying command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + cmd.extend(['--verify', signature_filename, data_filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd + + def verify_signature(self, signature_filename, data_filename, + keystore=None): + """ + Verify a signature for a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: True if the signature was verified, else False. + """ + if not self.gpg: + raise DistlibException('verification unavailable because gpg ' + 'unavailable') + cmd = self.get_verify_command(signature_filename, data_filename, + keystore) + rc, stdout, stderr = self.run_command(cmd) + if rc not in (0, 1): + raise DistlibException('verify command failed with error ' + 'code %s' % rc) + return rc == 0 + + def download_file(self, url, destfile, digest=None, reporthook=None): + """ + This is a convenience method for downloading a file from an URL. + Normally, this will be a file from the index, though currently + no check is made for this (i.e. a file can be downloaded from + anywhere). + + The method is just like the :func:`urlretrieve` function in the + standard library, except that it allows digest computation to be + done during download and checking that the downloaded data + matched any expected value. + + :param url: The URL of the file to be downloaded (assumed to be + available via an HTTP GET request). + :param destfile: The pathname where the downloaded file is to be + saved. + :param digest: If specified, this must be a (hasher, value) + tuple, where hasher is the algorithm used (e.g. + ``'md5'``) and ``value`` is the expected value. + :param reporthook: The same as for :func:`urlretrieve` in the + standard library. + """ + if digest is None: + digester = None + logger.debug('No digest specified') + else: + if isinstance(digest, (list, tuple)): + hasher, digest = digest + else: + hasher = 'md5' + digester = getattr(hashlib, hasher)() + logger.debug('Digest specified: %s' % digest) + # The following code is equivalent to urlretrieve. + # We need to do it this way so that we can compute the + # digest of the file as we go. + with open(destfile, 'wb') as dfp: + # addinfourl is not a context manager on 2.x + # so we have to use try/finally + sfp = self.send_request(Request(url)) + try: + headers = sfp.info() + blocksize = 8192 + size = -1 + read = 0 + blocknum = 0 + if "content-length" in headers: + size = int(headers["Content-Length"]) + if reporthook: + reporthook(blocknum, blocksize, size) + while True: + block = sfp.read(blocksize) + if not block: + break + read += len(block) + dfp.write(block) + if digester: + digester.update(block) + blocknum += 1 + if reporthook: + reporthook(blocknum, blocksize, size) + finally: + sfp.close() + + # check that we got the whole file, if we can + if size >= 0 and read < size: + raise DistlibException( + 'retrieval incomplete: got only %d out of %d bytes' + % (read, size)) + # if we have a digest, it must match. + if digester: + actual = digester.hexdigest() + if digest != actual: + raise DistlibException('%s digest mismatch for %s: expected ' + '%s, got %s' % (hasher, destfile, + digest, actual)) + logger.debug('Digest verified: %s', digest) + + def send_request(self, req): + """ + Send a standard library :class:`Request` to PyPI and return its + response. + + :param req: The request to send. + :return: The HTTP response from PyPI (a standard library HTTPResponse). + """ + handlers = [] + if self.password_handler: + handlers.append(self.password_handler) + if self.ssl_verifier: + handlers.append(self.ssl_verifier) + opener = build_opener(*handlers) + return opener.open(req) + + def encode_request(self, fields, files): + """ + Encode fields and files for posting to an HTTP server. + + :param fields: The fields to send as a list of (fieldname, value) + tuples. + :param files: The files to send as a list of (fieldname, filename, + file_bytes) tuple. + """ + # Adapted from packaging, which in turn was adapted from + # http://code.activestate.com/recipes/146306 + + parts = [] + boundary = self.boundary + for k, values in fields: + if not isinstance(values, (list, tuple)): + values = [values] + + for v in values: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"' % + k).encode('utf-8'), + b'', + v.encode('utf-8'))) + for key, filename, value in files: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"; filename="%s"' % + (key, filename)).encode('utf-8'), + b'', + value)) + + parts.extend((b'--' + boundary + b'--', b'')) + + body = b'\r\n'.join(parts) + ct = b'multipart/form-data; boundary=' + boundary + headers = { + 'Content-type': ct, + 'Content-length': str(len(body)) + } + return Request(self.url, body, headers) + + def search(self, terms, operator=None): + if isinstance(terms, string_types): + terms = {'name': terms} + if self.rpc_proxy is None: + self.rpc_proxy = ServerProxy(self.url, timeout=3.0) + return self.rpc_proxy.search(terms, operator or 'and') diff --git a/src/build_utils/distlib/locators.py b/src/build_utils/distlib/locators.py new file mode 100644 index 0000000000..e84075917c --- /dev/null +++ b/src/build_utils/distlib/locators.py @@ -0,0 +1,1195 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# + +import gzip +from io import BytesIO +import json +import logging +import os +import posixpath +import re +try: + import threading +except ImportError: + import dummy_threading as threading +import zlib + +from . import DistlibException +from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, + queue, quote, unescape, string_types, build_opener, + HTTPRedirectHandler as BaseRedirectHandler, + Request, HTTPError, URLError) +from .database import Distribution, DistributionPath, make_dist +from .metadata import Metadata +from .util import (cached_property, parse_credentials, ensure_slash, + split_filename, get_project_data, parse_requirement, + parse_name_and_version, ServerProxy) +from .version import get_scheme, UnsupportedVersionError +from .wheel import Wheel, is_compatible + +logger = logging.getLogger(__name__) + +HASHER_HASH = re.compile('^(\w+)=([a-f0-9]+)') +CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I) +HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml') +DEFAULT_INDEX = 'http://python.org/pypi' + +def get_all_distribution_names(url=None): + """ + Return all distribution names known by an index. + :param url: The URL of the index. + :return: A list of all known distribution names. + """ + if url is None: + url = DEFAULT_INDEX + client = ServerProxy(url, timeout=3.0) + return client.list_packages() + +class RedirectHandler(BaseRedirectHandler): + """ + A class to work around a bug in some Python 3.2.x releases. + """ + # There's a bug in the base version for some 3.2.x + # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header + # returns e.g. /abc, it bails because it says the scheme '' + # is bogus, when actually it should use the request's + # URL for the scheme. See Python issue #13696. + def http_error_302(self, req, fp, code, msg, headers): + # Some servers (incorrectly) return multiple Location headers + # (so probably same goes for URI). Use first header. + newurl = None + for key in ('location', 'uri'): + if key in headers: + newurl = headers[key] + break + if newurl is None: + return + urlparts = urlparse(newurl) + if urlparts.scheme == '': + newurl = urljoin(req.get_full_url(), newurl) + if hasattr(headers, 'replace_header'): + headers.replace_header(key, newurl) + else: + headers[key] = newurl + return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, + headers) + + http_error_301 = http_error_303 = http_error_307 = http_error_302 + +class Locator(object): + """ + A base class for locators - things that locate distributions. + """ + source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz') + binary_extensions = ('.egg', '.exe', '.whl') + excluded_extensions = ('.pdf',) + + # A list of tags indicating which wheels you want to match. The default + # value of None matches against the tags compatible with the running + # Python. If you want to match other values, set wheel_tags on a locator + # instance to a list of tuples (pyver, abi, arch) which you want to match. + wheel_tags = None + + downloadable_extensions = source_extensions + ('.whl',) + + def __init__(self, scheme='default'): + """ + Initialise an instance. + :param scheme: Because locators look for most recent versions, they + need to know the version scheme to use. This specifies + the current PEP-recommended scheme - use ``'legacy'`` + if you need to support existing distributions on PyPI. + """ + self._cache = {} + self.scheme = scheme + # Because of bugs in some of the handlers on some of the platforms, + # we use our own opener rather than just using urlopen. + self.opener = build_opener(RedirectHandler()) + # If get_project() is called from locate(), the matcher instance + # is set from the requirement passed to locate(). See issue #18 for + # why this can be useful to know. + self.matcher = None + + def clear_cache(self): + self._cache.clear() + + def _get_scheme(self): + return self._scheme + + def _set_scheme(self, value): + self._scheme = value + + scheme = property(_get_scheme, _set_scheme) + + def _get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This should be implemented in subclasses. + + If called from a locate() request, self.matcher will be set to a + matcher for the requirement to satisfy, otherwise it will be None. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This calls _get_project to do all the work, and just implements a caching layer on top. + """ + if self._cache is None: + result = self._get_project(name) + elif name in self._cache: + result = self._cache[name] + else: + result = self._get_project(name) + self._cache[name] = result + return result + + def score_url(self, url): + """ + Give an url a score which can be used to choose preferred URLs + for a given project release. + """ + t = urlparse(url) + return (t.scheme != 'https', 'pypi.python.org' in t.netloc, + posixpath.basename(t.path)) + + def prefer_url(self, url1, url2): + """ + Choose one of two URLs where both are candidates for distribution + archives for the same version of a distribution (for example, + .tar.gz vs. zip). + + The current implement favours http:// URLs over https://, archives + from PyPI over those from other locations and then the archive name. + """ + result = url2 + if url1: + s1 = self.score_url(url1) + s2 = self.score_url(url2) + if s1 > s2: + result = url1 + if result != url2: + logger.debug('Not replacing %r with %r', url1, url2) + else: + logger.debug('Replacing %r with %r', url1, url2) + return result + + def split_filename(self, filename, project_name): + """ + Attempt to split a filename in project name, version and Python version. + """ + return split_filename(filename, project_name) + + def convert_url_to_download_info(self, url, project_name): + """ + See if a URL is a candidate for a download URL for a project (the URL + has typically been scraped from an HTML page). + + If it is, a dictionary is returned with keys "name", "version", + "filename" and "url"; otherwise, None is returned. + """ + def same_project(name1, name2): + name1, name2 = name1.lower(), name2.lower() + if name1 == name2: + result = True + else: + # distribute replaces '-' by '_' in project names, so it + # can tell where the version starts in a filename. + result = name1.replace('_', '-') == name2.replace('_', '-') + return result + + result = None + scheme, netloc, path, params, query, frag = urlparse(url) + if frag.lower().startswith('egg='): + logger.debug('%s: version hint in fragment: %r', + project_name, frag) + m = HASHER_HASH.match(frag) + if m: + algo, digest = m.groups() + else: + algo, digest = None, None + origpath = path + if path and path[-1] == '/': + path = path[:-1] + if path.endswith('.whl'): + try: + wheel = Wheel(path) + if is_compatible(wheel, self.wheel_tags): + if project_name is None: + include = True + else: + include = same_project(wheel.name, project_name) + if include: + result = { + 'name': wheel.name, + 'version': wheel.version, + 'filename': wheel.filename, + 'url': urlunparse((scheme, netloc, origpath, + params, query, '')), + 'python-version': ', '.join( + ['.'.join(list(v[2:])) for v in wheel.pyver]), + } + except Exception as e: + logger.warning('invalid path for wheel: %s', path) + elif path.endswith(self.downloadable_extensions): + path = filename = posixpath.basename(path) + for ext in self.downloadable_extensions: + if path.endswith(ext): + path = path[:-len(ext)] + t = self.split_filename(path, project_name) + if not t: + logger.debug('No match for project/version: %s', path) + else: + name, version, pyver = t + if not project_name or same_project(project_name, name): + result = { + 'name': name, + 'version': version, + 'filename': filename, + 'url': urlunparse((scheme, netloc, origpath, + params, query, '')), + #'packagetype': 'sdist', + } + if pyver: + result['python-version'] = pyver + break + if result and algo: + result['%s_digest' % algo] = digest + return result + + def _get_digest(self, info): + """ + Get a digest from a dictionary by looking at keys of the form + 'algo_digest'. + + Returns a 2-tuple (algo, digest) if found, else None. Currently + looks only for SHA256, then MD5. + """ + result = None + for algo in ('sha256', 'md5'): + key = '%s_digest' % algo + if key in info: + result = (algo, info[key]) + break + return result + + def _update_version_data(self, result, info): + """ + Update a result dictionary (the final result from _get_project) with a dictionary for a + specific version, whih typically holds information gleaned from a filename or URL for an + archive for the distribution. + """ + name = info.pop('name') + version = info.pop('version') + if version in result: + dist = result[version] + md = dist.metadata + else: + dist = make_dist(name, version, scheme=self.scheme) + md = dist.metadata + dist.digest = self._get_digest(info) + if md.source_url != info['url']: + md.source_url = self.prefer_url(md.source_url, info['url']) + dist.locator = self + result[version] = dist + + def locate(self, requirement, prereleases=False): + """ + Find the most recent distribution which matches the given + requirement. + + :param requirement: A requirement of the form 'foo (1.0)' or perhaps + 'foo (>= 1.0, < 2.0, != 1.3)' + :param prereleases: If ``True``, allow pre-release versions + to be located. Otherwise, pre-release versions + are not returned. + :return: A :class:`Distribution` instance, or ``None`` if no such + distribution could be located. + """ + result = None + r = parse_requirement(requirement) + if r is None: + raise DistlibException('Not a valid requirement: %r' % requirement) + scheme = get_scheme(self.scheme) + self.matcher = matcher = scheme.matcher(r.requirement) + logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__) + versions = self.get_project(r.name) + if versions: + # sometimes, versions are invalid + slist = [] + vcls = matcher.version_class + for k in versions: + try: + if not matcher.match(k): + logger.debug('%s did not match %r', matcher, k) + else: + if prereleases or not vcls(k).is_prerelease: + slist.append(k) + else: + logger.debug('skipping pre-release ' + 'version %s of %s', k, matcher.name) + except Exception: + logger.warning('error matching %s with %r', matcher, k) + pass # slist.append(k) + if len(slist) > 1: + slist = sorted(slist, key=scheme.key) + if slist: + logger.debug('sorted list: %s', slist) + result = versions[slist[-1]] + if result and r.extras: + result.extras = r.extras + self.matcher = None + return result + + +class PyPIRPCLocator(Locator): + """ + This locator uses XML-RPC to locate distributions. It therefore + cannot be used with simple mirrors (that only mirror file content). + """ + def __init__(self, url, **kwargs): + """ + Initialise an instance. + + :param url: The URL to use for XML-RPC. + :param kwargs: Passed to the superclass constructor. + """ + super(PyPIRPCLocator, self).__init__(**kwargs) + self.base_url = url + self.client = ServerProxy(url, timeout=3.0) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + return set(self.client.list_packages()) + + def _get_project(self, name): + result = {} + versions = self.client.package_releases(name, True) + for v in versions: + urls = self.client.release_urls(name, v) + data = self.client.release_data(name, v) + metadata = Metadata(scheme=self.scheme) + metadata.name = data['name'] + metadata.version = data['version'] + metadata.license = data.get('license') + metadata.keywords = data.get('keywords', []) + metadata.summary = data.get('summary') + dist = Distribution(metadata) + if urls: + info = urls[0] + metadata.source_url = info['url'] + dist.digest = self._get_digest(info) + dist.locator = self + result[v] = dist + return result + +class PyPIJSONLocator(Locator): + """ + This locator uses PyPI's JSON interface. It's very limited in functionality + nad probably not worth using. + """ + def __init__(self, url, **kwargs): + super(PyPIJSONLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {} + url = urljoin(self.base_url, '%s/json' % quote(name)) + try: + resp = self.opener.open(url) + data = resp.read().decode() # for now + d = json.loads(data) + md = Metadata(scheme=self.scheme) + data = d['info'] + md.name = data['name'] + md.version = data['version'] + md.license = data.get('license') + md.keywords = data.get('keywords', []) + md.summary = data.get('summary') + dist = Distribution(md) + urls = d['urls'] + if urls: + info = urls[0] + md.source_url = info['url'] + dist.digest = self._get_digest(info) + dist.locator = self + result[md.version] = dist + except Exception as e: + logger.exception('JSON fetch failed: %s', e) + return result + + +class Page(object): + """ + This class represents a scraped HTML page. + """ + # The following slightly hairy-looking regex just looks for the contents of + # an anchor link, which has an attribute "href" either immediately preceded + # or immediately followed by a "rel" attribute. The attribute values can be + # declared with double quotes, single quotes or no quotes - which leads to + # the length of the expression. + _href = re.compile(""" +(rel\s*=\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\s\n]*))\s+)? +href\s*=\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\s\n]*)) +(\s+rel\s*=\s*(?:"(?P[^"]*)"|'(?P[^']*)'|(?P[^>\s\n]*)))? +""", re.I | re.S | re.X) + _base = re.compile(r"""]+)""", re.I | re.S) + + def __init__(self, data, url): + """ + Initialise an instance with the Unicode page contents and the URL they + came from. + """ + self.data = data + self.base_url = self.url = url + m = self._base.search(self.data) + if m: + self.base_url = m.group(1) + + _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) + + @cached_property + def links(self): + """ + Return the URLs of all the links on a page together with information + about their "rel" attribute, for determining which ones to treat as + downloads and which ones to queue for further scraping. + """ + def clean(url): + "Tidy up an URL." + scheme, netloc, path, params, query, frag = urlparse(url) + return urlunparse((scheme, netloc, quote(path), + params, query, frag)) + + result = set() + for match in self._href.finditer(self.data): + d = match.groupdict('') + rel = (d['rel1'] or d['rel2'] or d['rel3'] or + d['rel4'] or d['rel5'] or d['rel6']) + url = d['url1'] or d['url2'] or d['url3'] + url = urljoin(self.base_url, url) + url = unescape(url) + url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url) + result.add((url, rel)) + # We sort the result, hoping to bring the most recent versions + # to the front + result = sorted(result, key=lambda t: t[0], reverse=True) + return result + + +class SimpleScrapingLocator(Locator): + """ + A locator which scrapes HTML pages to locate downloads for a distribution. + This runs multiple threads to do the I/O; performance is at least as good + as pip's PackageFinder, which works in an analogous fashion. + """ + + # These are used to deal with various Content-Encoding schemes. + decoders = { + 'deflate': zlib.decompress, + 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(d)).read(), + 'none': lambda b: b, + } + + def __init__(self, url, timeout=None, num_workers=10, **kwargs): + """ + Initialise an instance. + :param url: The root URL to use for scraping. + :param timeout: The timeout, in seconds, to be applied to requests. + This defaults to ``None`` (no timeout specified). + :param num_workers: The number of worker threads you want to do I/O, + This defaults to 10. + :param kwargs: Passed to the superclass. + """ + super(SimpleScrapingLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + self.timeout = timeout + self._page_cache = {} + self._seen = set() + self._to_fetch = queue.Queue() + self._bad_hosts = set() + self.skip_externals = False + self.num_workers = num_workers + self._lock = threading.RLock() + # See issue #45: we need to be resilient when the locator is used + # in a thread, e.g. with concurrent.futures. We can't use self._lock + # as it is for coordinating our internal threads - the ones created + # in _prepare_threads. + self._gplock = threading.RLock() + + def _prepare_threads(self): + """ + Threads are created only when get_project is called, and terminate + before it returns. They are there primarily to parallelise I/O (i.e. + fetching web pages). + """ + self._threads = [] + for i in range(self.num_workers): + t = threading.Thread(target=self._fetch) + t.setDaemon(True) + t.start() + self._threads.append(t) + + def _wait_threads(self): + """ + Tell all the threads to terminate (by sending a sentinel value) and + wait for them to do so. + """ + # Note that you need two loops, since you can't say which + # thread will get each sentinel + for t in self._threads: + self._to_fetch.put(None) # sentinel + for t in self._threads: + t.join() + self._threads = [] + + def _get_project(self, name): + result = {} + with self._gplock: + self.result = result + self.project_name = name + url = urljoin(self.base_url, '%s/' % quote(name)) + self._seen.clear() + self._page_cache.clear() + self._prepare_threads() + try: + logger.debug('Queueing %s', url) + self._to_fetch.put(url) + self._to_fetch.join() + finally: + self._wait_threads() + del self.result + return result + + platform_dependent = re.compile(r'\b(linux-(i\d86|x86_64|arm\w+)|' + r'win(32|-amd64)|macosx-?\d+)\b', re.I) + + def _is_platform_dependent(self, url): + """ + Does an URL refer to a platform-specific download? + """ + return self.platform_dependent.search(url) + + def _process_download(self, url): + """ + See if an URL is a suitable download for a project. + + If it is, register information in the result dictionary (for + _get_project) about the specific version it's for. + + Note that the return value isn't actually used other than as a boolean + value. + """ + if self._is_platform_dependent(url): + info = None + else: + info = self.convert_url_to_download_info(url, self.project_name) + logger.debug('process_download: %s -> %s', url, info) + if info: + with self._lock: # needed because self.result is shared + self._update_version_data(self.result, info) + return info + + def _should_queue(self, link, referrer, rel): + """ + Determine whether a link URL from a referring page and with a + particular "rel" attribute should be queued for scraping. + """ + scheme, netloc, path, _, _, _ = urlparse(link) + if path.endswith(self.source_extensions + self.binary_extensions + + self.excluded_extensions): + result = False + elif self.skip_externals and not link.startswith(self.base_url): + result = False + elif not referrer.startswith(self.base_url): + result = False + elif rel not in ('homepage', 'download'): + result = False + elif scheme not in ('http', 'https', 'ftp'): + result = False + elif self._is_platform_dependent(link): + result = False + else: + host = netloc.split(':', 1)[0] + if host.lower() == 'localhost': + result = False + else: + result = True + logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, + referrer, result) + return result + + def _fetch(self): + """ + Get a URL to fetch from the work queue, get the HTML page, examine its + links for download candidates and candidates for further scraping. + + This is a handy method to run in a thread. + """ + while True: + url = self._to_fetch.get() + try: + if url: + page = self.get_page(url) + if page is None: # e.g. after an error + continue + for link, rel in page.links: + if link not in self._seen: + self._seen.add(link) + if (not self._process_download(link) and + self._should_queue(link, url, rel)): + logger.debug('Queueing %s from %s', link, url) + self._to_fetch.put(link) + finally: + # always do this, to avoid hangs :-) + self._to_fetch.task_done() + if not url: + #logger.debug('Sentinel seen, quitting.') + break + + def get_page(self, url): + """ + Get the HTML for an URL, possibly from an in-memory cache. + + XXX TODO Note: this cache is never actually cleared. It's assumed that + the data won't get stale over the lifetime of a locator instance (not + necessarily true for the default_locator). + """ + # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api + scheme, netloc, path, _, _, _ = urlparse(url) + if scheme == 'file' and os.path.isdir(url2pathname(path)): + url = urljoin(ensure_slash(url), 'index.html') + + if url in self._page_cache: + result = self._page_cache[url] + logger.debug('Returning %s from cache: %s', url, result) + else: + host = netloc.split(':', 1)[0] + result = None + if host in self._bad_hosts: + logger.debug('Skipping %s due to bad host %s', url, host) + else: + req = Request(url, headers={'Accept-encoding': 'identity'}) + try: + logger.debug('Fetching %s', url) + resp = self.opener.open(req, timeout=self.timeout) + logger.debug('Fetched %s', url) + headers = resp.info() + content_type = headers.get('Content-Type', '') + if HTML_CONTENT_TYPE.match(content_type): + final_url = resp.geturl() + data = resp.read() + encoding = headers.get('Content-Encoding') + if encoding: + decoder = self.decoders[encoding] # fail if not found + data = decoder(data) + encoding = 'utf-8' + m = CHARSET.search(content_type) + if m: + encoding = m.group(1) + try: + data = data.decode(encoding) + except UnicodeError: + data = data.decode('latin-1') # fallback + result = Page(data, final_url) + self._page_cache[final_url] = result + except HTTPError as e: + if e.code != 404: + logger.exception('Fetch failed: %s: %s', url, e) + except URLError as e: + logger.exception('Fetch failed: %s: %s', url, e) + with self._lock: + self._bad_hosts.add(host) + except Exception as e: + logger.exception('Fetch failed: %s: %s', url, e) + finally: + self._page_cache[url] = result # even if None (failure) + return result + + _distname_re = re.compile(']*>([^<]+)<') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + page = self.get_page(self.base_url) + if not page: + raise DistlibException('Unable to get %s' % self.base_url) + for match in self._distname_re.finditer(page.data): + result.add(match.group(1)) + return result + +class DirectoryLocator(Locator): + """ + This class locates distributions in a directory tree. + """ + + def __init__(self, path, **kwargs): + """ + Initialise an instance. + :param path: The root of the directory tree to search. + :param kwargs: Passed to the superclass constructor, + except for: + * recursive - if True (the default), subdirectories are + recursed into. If False, only the top-level directory + is searched, + """ + self.recursive = kwargs.pop('recursive', True) + super(DirectoryLocator, self).__init__(**kwargs) + path = os.path.abspath(path) + if not os.path.isdir(path): + raise DistlibException('Not a directory: %r' % path) + self.base_dir = path + + def should_include(self, filename, parent): + """ + Should a filename be considered as a candidate for a distribution + archive? As well as the filename, the directory which contains it + is provided, though not used by the current implementation. + """ + return filename.endswith(self.downloadable_extensions) + + def _get_project(self, name): + result = {} + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', + pathname2url(os.path.abspath(fn)), + '', '', '')) + info = self.convert_url_to_download_info(url, name) + if info: + self._update_version_data(result, info) + if not self.recursive: + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', + pathname2url(os.path.abspath(fn)), + '', '', '')) + info = self.convert_url_to_download_info(url, None) + if info: + result.add(info['name']) + if not self.recursive: + break + return result + +class JSONLocator(Locator): + """ + This locator uses special extended metadata (not available on PyPI) and is + the basis of performant dependency resolution in distlib. Other locators + require archive downloads before dependencies can be determined! As you + might imagine, that can be slow. + """ + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {} + data = get_project_data(name) + if data: + for info in data.get('files', []): + if info['ptype'] != 'sdist' or info['pyversion'] != 'source': + continue + # We don't store summary in project metadata as it makes + # the data bigger for no benefit during dependency + # resolution + dist = make_dist(data['name'], info['version'], + summary=data.get('summary', + 'Placeholder for summary'), + scheme=self.scheme) + md = dist.metadata + md.source_url = info['url'] + # TODO SHA256 digest + if 'digest' in info and info['digest']: + dist.digest = ('md5', info['digest']) + md.dependencies = info.get('requirements', {}) + dist.exports = info.get('exports', {}) + result[dist.version] = dist + return result + +class DistPathLocator(Locator): + """ + This locator finds installed distributions in a path. It can be useful for + adding to an :class:`AggregatingLocator`. + """ + def __init__(self, distpath, **kwargs): + """ + Initialise an instance. + + :param distpath: A :class:`DistributionPath` instance to search. + """ + super(DistPathLocator, self).__init__(**kwargs) + assert isinstance(distpath, DistributionPath) + self.distpath = distpath + + def _get_project(self, name): + dist = self.distpath.get_distribution(name) + if dist is None: + result = {} + else: + result = { dist.version: dist } + return result + + +class AggregatingLocator(Locator): + """ + This class allows you to chain and/or merge a list of locators. + """ + def __init__(self, *locators, **kwargs): + """ + Initialise an instance. + + :param locators: The list of locators to search. + :param kwargs: Passed to the superclass constructor, + except for: + * merge - if False (the default), the first successful + search from any of the locators is returned. If True, + the results from all locators are merged (this can be + slow). + """ + self.merge = kwargs.pop('merge', False) + self.locators = locators + super(AggregatingLocator, self).__init__(**kwargs) + + def clear_cache(self): + super(AggregatingLocator, self).clear_cache() + for locator in self.locators: + locator.clear_cache() + + def _set_scheme(self, value): + self._scheme = value + for locator in self.locators: + locator.scheme = value + + scheme = property(Locator.scheme.fget, _set_scheme) + + def _get_project(self, name): + result = {} + for locator in self.locators: + d = locator.get_project(name) + if d: + if self.merge: + result.update(d) + else: + # See issue #18. If any dists are found and we're looking + # for specific constraints, we only return something if + # a match is found. For example, if a DirectoryLocator + # returns just foo (1.0) while we're looking for + # foo (>= 2.0), we'll pretend there was nothing there so + # that subsequent locators can be queried. Otherwise we + # would just return foo (1.0) which would then lead to a + # failure to find foo (>= 2.0), because other locators + # weren't searched. Note that this only matters when + # merge=False. + if self.matcher is None: + found = True + else: + found = False + for k in d: + if self.matcher.match(k): + found = True + break + if found: + result = d + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for locator in self.locators: + try: + result |= locator.get_distribution_names() + except NotImplementedError: + pass + return result + + +# We use a legacy scheme simply because most of the dists on PyPI use legacy +# versions which don't conform to PEP 426 / PEP 440. +default_locator = AggregatingLocator( + JSONLocator(), + SimpleScrapingLocator('https://pypi.python.org/simple/', + timeout=3.0), + scheme='legacy') + +locate = default_locator.locate + +NAME_VERSION_RE = re.compile(r'(?P[\w-]+)\s*' + r'\(\s*(==\s*)?(?P[^)]+)\)$') + +class DependencyFinder(object): + """ + Locate dependencies for distributions. + """ + + def __init__(self, locator=None): + """ + Initialise an instance, using the specified locator + to locate distributions. + """ + self.locator = locator or default_locator + self.scheme = get_scheme(self.locator.scheme) + + def add_distribution(self, dist): + """ + Add a distribution to the finder. This will update internal information + about who provides what. + :param dist: The distribution to add. + """ + logger.debug('adding distribution %s', dist) + name = dist.key + self.dists_by_name[name] = dist + self.dists[(name, dist.version)] = dist + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + self.provided.setdefault(name, set()).add((version, dist)) + + def remove_distribution(self, dist): + """ + Remove a distribution from the finder. This will update internal + information about who provides what. + :param dist: The distribution to remove. + """ + logger.debug('removing distribution %s', dist) + name = dist.key + del self.dists_by_name[name] + del self.dists[(name, dist.version)] + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Remove from provided: %s, %s, %s', name, version, dist) + s = self.provided[name] + s.remove((version, dist)) + if not s: + del self.provided[name] + + def get_matcher(self, reqt): + """ + Get a version matcher for a requirement. + :param reqt: The requirement + :type reqt: str + :return: A version matcher (an instance of + :class:`distlib.version.Matcher`). + """ + try: + matcher = self.scheme.matcher(reqt) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + name = reqt.split()[0] + matcher = self.scheme.matcher(name) + return matcher + + def find_providers(self, reqt): + """ + Find the distributions which can fulfill a requirement. + + :param reqt: The requirement. + :type reqt: str + :return: A set of distribution which can fulfill the requirement. + """ + matcher = self.get_matcher(reqt) + name = matcher.key # case-insensitive + result = set() + provided = self.provided + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + result.add(provider) + break + return result + + def try_to_replace(self, provider, other, problems): + """ + Attempt to replace one provider with another. This is typically used + when resolving dependencies from multiple sources, e.g. A requires + (B >= 1.0) while C requires (B >= 1.1). + + For successful replacement, ``provider`` must meet all the requirements + which ``other`` fulfills. + + :param provider: The provider we are trying to replace with. + :param other: The provider we're trying to replace. + :param problems: If False is returned, this will contain what + problems prevented replacement. This is currently + a tuple of the literal string 'cantreplace', + ``provider``, ``other`` and the set of requirements + that ``provider`` couldn't fulfill. + :return: True if we can replace ``other`` with ``provider``, else + False. + """ + rlist = self.reqts[other] + unmatched = set() + for s in rlist: + matcher = self.get_matcher(s) + if not matcher.match(provider.version): + unmatched.add(s) + if unmatched: + # can't replace other with provider + problems.add(('cantreplace', provider, other, + frozenset(unmatched))) + result = False + else: + # can replace other with provider + self.remove_distribution(other) + del self.reqts[other] + for s in rlist: + self.reqts.setdefault(provider, set()).add(s) + self.add_distribution(provider) + result = True + return result + + def find(self, requirement, meta_extras=None, prereleases=False): + """ + Find a distribution and all distributions it depends on. + + :param requirement: The requirement specifying the distribution to + find, or a Distribution instance. + :param meta_extras: A list of meta extras such as :test:, :build: and + so on. + :param prereleases: If ``True``, allow pre-release versions to be + returned - otherwise, don't return prereleases + unless they're all that's available. + + Return a set of :class:`Distribution` instances and a set of + problems. + + The distributions returned should be such that they have the + :attr:`required` attribute set to ``True`` if they were + from the ``requirement`` passed to ``find()``, and they have the + :attr:`build_time_dependency` attribute set to ``True`` unless they + are post-installation dependencies of the ``requirement``. + + The problems should be a tuple consisting of the string + ``'unsatisfied'`` and the requirement which couldn't be satisfied + by any distribution known to the locator. + """ + + self.provided = {} + self.dists = {} + self.dists_by_name = {} + self.reqts = {} + + meta_extras = set(meta_extras or []) + if ':*:' in meta_extras: + meta_extras.remove(':*:') + # :meta: and :run: are implicitly included + meta_extras |= set([':test:', ':build:', ':dev:']) + + if isinstance(requirement, Distribution): + dist = odist = requirement + logger.debug('passed %s as requirement', odist) + else: + dist = odist = self.locator.locate(requirement, + prereleases=prereleases) + if dist is None: + raise DistlibException('Unable to locate %r' % requirement) + logger.debug('located %s', odist) + dist.requested = True + problems = set() + todo = set([dist]) + install_dists = set([odist]) + while todo: + dist = todo.pop() + name = dist.key # case-insensitive + if name not in self.dists_by_name: + self.add_distribution(dist) + else: + #import pdb; pdb.set_trace() + other = self.dists_by_name[name] + if other != dist: + self.try_to_replace(dist, other, problems) + + ireqts = dist.run_requires | dist.meta_requires + sreqts = dist.build_requires + ereqts = set() + if dist in install_dists: + for key in ('test', 'build', 'dev'): + e = ':%s:' % key + if e in meta_extras: + ereqts |= getattr(dist, '%s_requires' % key) + all_reqts = ireqts | sreqts | ereqts + for r in all_reqts: + providers = self.find_providers(r) + if not providers: + logger.debug('No providers found for %r', r) + provider = self.locator.locate(r, prereleases=prereleases) + # If no provider is found and we didn't consider + # prereleases, consider them now. + if provider is None and not prereleases: + provider = self.locator.locate(r, prereleases=True) + if provider is None: + logger.debug('Cannot satisfy %r', r) + problems.add(('unsatisfied', r)) + else: + n, v = provider.key, provider.version + if (n, v) not in self.dists: + todo.add(provider) + providers.add(provider) + if r in ireqts and dist in install_dists: + install_dists.add(provider) + logger.debug('Adding %s to install_dists', + provider.name_and_version) + for p in providers: + name = p.key + if name not in self.dists_by_name: + self.reqts.setdefault(p, set()).add(r) + else: + other = self.dists_by_name[name] + if other != p: + # see if other can be replaced by p + self.try_to_replace(p, other, problems) + + dists = set(self.dists.values()) + for dist in dists: + dist.build_time_dependency = dist not in install_dists + if dist.build_time_dependency: + logger.debug('%s is a build-time dependency only.', + dist.name_and_version) + logger.debug('find done for %s', odist) + return dists, problems diff --git a/src/build_utils/distlib/manifest.py b/src/build_utils/distlib/manifest.py new file mode 100644 index 0000000000..c6b98c59a3 --- /dev/null +++ b/src/build_utils/distlib/manifest.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2013 Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Class representing the list of files in a distribution. + +Equivalent to distutils.filelist, but fixes some problems. +""" +import fnmatch +import logging +import os +import re + +from . import DistlibException +from .compat import fsdecode +from .util import convert_path + + +__all__ = ['Manifest'] + +logger = logging.getLogger(__name__) + +# 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, base=None): + """ + Initialise an instance. + + :param base: The base directory to explore under. + """ + self.base = os.path.abspath(os.path.normpath(base or os.getcwd())) + self.prefix = self.base + os.sep + self.allfiles = None + self.files = set() + + # + # Public API + # + + def findall(self): + """Find all files under the base and set ``allfiles`` to the absolute + pathnames of files found. + """ + from stat import S_ISREG, S_ISDIR, S_ISLNK + + self.allfiles = allfiles = [] + root = self.base + stack = [root] + pop = stack.pop + push = stack.append + + while stack: + root = pop() + names = os.listdir(root) + + for name in names: + fullname = os.path.join(root, name) + + # Avoid excess stat calls -- just one will do, thank you! + stat = os.stat(fullname) + mode = stat.st_mode + if S_ISREG(mode): + allfiles.append(fsdecode(fullname)) + elif S_ISDIR(mode) and not S_ISLNK(mode): + push(fullname) + + def add(self, item): + """ + Add a file to the manifest. + + :param item: The pathname to add. This can be relative to the base. + """ + if not item.startswith(self.prefix): + item = os.path.join(self.base, item) + self.files.add(os.path.normpath(item)) + + def add_many(self, items): + """ + Add a list of files to the manifest. + + :param items: The pathnames to add. These can be relative to the base. + """ + for item in items: + self.add(item) + + def sorted(self, wantdirs=False): + """ + Return sorted files in directory order + """ + + def add_dir(dirs, d): + dirs.add(d) + logger.debug('add_dir added %s', d) + if d != self.base: + parent, _ = os.path.split(d) + assert parent not in ('', '/') + add_dir(dirs, parent) + + result = set(self.files) # make a copy! + if wantdirs: + dirs = set() + for f in result: + add_dir(dirs, os.path.dirname(f)) + result |= dirs + return [os.path.join(*path_tuple) for path_tuple in + sorted(os.path.split(path) for path in result)] + + def clear(self): + """Clear all collected files.""" + self.files = set() + self.allfiles = [] + + def process_directive(self, directive): + """ + Process a directive which either adds some files from ``allfiles`` to + ``files``, or removes some files from ``files``. + + :param directive: The directive to process. This should be in a format + compatible with distutils ``MANIFEST.in`` files: + + http://docs.python.org/distutils/sourcedist.html#commands + """ + # 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 (dirpattern). + action, patterns, thedir, dirpattern = self._parse_directive(directive) + + # 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=thedir): + logger.warning('no files found matching %r ' + 'under directory %r', pattern, thedir) + + elif action == 'recursive-exclude': + for pattern in patterns: + if not self._exclude_pattern(pattern, prefix=thedir): + logger.warning('no previously-included files ' + 'matching %r found under directory %r', + pattern, thedir) + + elif action == 'graft': + if not self._include_pattern(None, prefix=dirpattern): + logger.warning('no directories found matching %r', + dirpattern) + + elif action == 'prune': + if not self._exclude_pattern(None, prefix=dirpattern): + logger.warning('no previously-included directories found ' + 'matching %r', dirpattern) + else: # pragma: no cover + # This should never happen, as it should be caught in + # _parse_template_line + raise DistlibException( + 'invalid action %r' % action) + + # + # Private API + # + + def _parse_directive(self, directive): + """ + Validate a directive. + :param directive: The directive to validate. + :return: A tuple of action, patterns, thedir, dir_patterns + """ + words = directive.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 = thedir = dir_pattern = None + + if action in ('include', 'exclude', + 'global-include', 'global-exclude'): + if len(words) < 2: + raise DistlibException( + '%r expects ...' % action) + + patterns = [convert_path(word) for word in words[1:]] + + elif action in ('recursive-include', 'recursive-exclude'): + if len(words) < 3: + raise DistlibException( + '%r expects

...' % action) + + thedir = convert_path(words[1]) + patterns = [convert_path(word) for word in words[2:]] + + elif action in ('graft', 'prune'): + if len(words) != 2: + raise DistlibException( + '%r expects a single ' % action) + + dir_pattern = convert_path(words[1]) + + else: + raise DistlibException('unknown action %r' % action) + + return action, patterns, thedir, dir_pattern + + 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? + found = False + pattern_re = self._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.add(name) + found = True + return found + + 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. + + This API is public to allow e.g. exclusion of SCM subdirs, e.g. when + packaging source distributions + """ + found = False + pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) + for f in list(self.files): + if pattern_re.search(f): + self.files.remove(f) + found = True + return found + + def _translate_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Translate a shell-like wildcard pattern to a compiled regular + expression. + + Return the compiled regex. If 'is_regex' true, + then 'pattern' is directly compiled to a regex (if it's a string) + or just returned as-is (assumes it's a regex object). + """ + if is_regex: + if isinstance(pattern, str): + return re.compile(pattern) + else: + return pattern + + if pattern: + pattern_re = self._glob_to_re(pattern) + else: + pattern_re = '' + + base = re.escape(os.path.join(self.base, '')) + if prefix is not None: + # ditch end of pattern character + empty_pattern = self._glob_to_re('') + prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)] + sep = os.sep + if os.sep == '\\': + sep = r'\\' + pattern_re = '^' + base + sep.join((prefix_re, + '.*' + pattern_re)) + else: # no prefix -- respect anchor flag + if anchor: + pattern_re = '^' + base + pattern_re + + return re.compile(pattern_re) + + def _glob_to_re(self, 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'((? y, + 'gte': lambda x, y: x >= y, + 'in': lambda x, y: x in y, + 'lt': lambda x, y: x < y, + 'lte': lambda x, y: x <= y, + 'not': lambda x: not x, + 'noteq': lambda x, y: x != y, + 'notin': lambda x, y: x not in y, + } + + allowed_values = { + 'sys_platform': sys.platform, + 'python_version': '%s.%s' % sys.version_info[:2], + # 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_in_venv': str(in_venv()), + 'platform_release': platform.release(), + 'platform_version': platform.version(), + 'platform_machine': platform.machine(), + 'platform_python_implementation': python_implementation(), + } + + def __init__(self, context=None): + """ + Initialise an instance. + + :param context: If specified, names are looked up in this mapping. + """ + self.context = context or {} + self.source = None + + def get_fragment(self, offset): + """ + Get the part of the source which is causing a problem. + """ + fragment_len = 10 + s = '%r' % (self.source[offset:offset + fragment_len]) + if offset + fragment_len < len(self.source): + s += '...' + return s + + def get_handler(self, node_type): + """ + Get a handler for the specified AST node type. + """ + return getattr(self, 'do_%s' % node_type, None) + + def evaluate(self, node, filename=None): + """ + Evaluate a source string or node, using ``filename`` when + displaying errors. + """ + if isinstance(node, string_types): + self.source = node + kwargs = {'mode': 'eval'} + if filename: + kwargs['filename'] = filename + try: + node = ast.parse(node, **kwargs) + except SyntaxError as e: + s = self.get_fragment(e.offset) + raise SyntaxError('syntax error %s' % s) + node_type = node.__class__.__name__.lower() + handler = self.get_handler(node_type) + if handler is None: + if self.source is None: + s = '(source not available)' + else: + s = self.get_fragment(node.col_offset) + raise SyntaxError("don't know how to evaluate %r %s" % ( + node_type, s)) + return handler(node) + + def get_attr_key(self, node): + assert isinstance(node, ast.Attribute), 'attribute node expected' + return '%s.%s' % (node.value.id, node.attr) + + def do_attribute(self, node): + if not isinstance(node.value, ast.Name): + valid = False + else: + key = self.get_attr_key(node) + valid = key in self.context or key in self.allowed_values + if not valid: + raise SyntaxError('invalid expression: %s' % key) + if key in self.context: + result = self.context[key] + else: + result = self.allowed_values[key] + return result + + def do_boolop(self, node): + result = self.evaluate(node.values[0]) + is_or = node.op.__class__ is ast.Or + is_and = node.op.__class__ is ast.And + assert is_or or is_and + if (is_and and result) or (is_or and not result): + for n in node.values[1:]: + result = self.evaluate(n) + if (is_or and result) or (is_and and not result): + break + return result + + def do_compare(self, node): + def sanity_check(lhsnode, rhsnode): + valid = True + if isinstance(lhsnode, ast.Str) and isinstance(rhsnode, ast.Str): + valid = False + #elif (isinstance(lhsnode, ast.Attribute) + # and isinstance(rhsnode, ast.Attribute)): + # klhs = self.get_attr_key(lhsnode) + # krhs = self.get_attr_key(rhsnode) + # valid = klhs != krhs + if not valid: + s = self.get_fragment(node.col_offset) + raise SyntaxError('Invalid comparison: %s' % s) + + lhsnode = node.left + lhs = self.evaluate(lhsnode) + result = True + for op, rhsnode in zip(node.ops, node.comparators): + sanity_check(lhsnode, rhsnode) + op = op.__class__.__name__.lower() + if op not in self.operators: + raise SyntaxError('unsupported operation: %r' % op) + rhs = self.evaluate(rhsnode) + result = self.operators[op](lhs, rhs) + if not result: + break + lhs = rhs + lhsnode = rhsnode + return result + + def do_expression(self, node): + return self.evaluate(node.body) + + def do_name(self, node): + valid = False + if node.id in self.context: + valid = True + result = self.context[node.id] + elif node.id in self.allowed_values: + valid = True + result = self.allowed_values[node.id] + if not valid: + raise SyntaxError('invalid expression: %s' % node.id) + return result + + def do_str(self, node): + return node.s + + +def interpret(marker, execution_context=None): + """ + Interpret a marker and return a result depending on environment. + + :param marker: The marker to interpret. + :type marker: str + :param execution_context: The context used for name lookup. + :type execution_context: mapping + """ + return Evaluator(execution_context).evaluate(marker.strip()) diff --git a/src/build_utils/distlib/metadata.py b/src/build_utils/distlib/metadata.py new file mode 100644 index 0000000000..6d987a2323 --- /dev/null +++ b/src/build_utils/distlib/metadata.py @@ -0,0 +1,1058 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Implementation of the Metadata for Python packages PEPs. + +Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental). +""" +from __future__ import unicode_literals + +import codecs +from email import message_from_file +import json +import logging +import re + + +from . import DistlibException, __version__ +from .compat import StringIO, string_types, text_type +from .markers import interpret +from .util import extract_by_key, get_extras +from .version import get_scheme, PEP426_VERSION_RE + +logger = logging.getLogger(__name__) + + +class MetadataMissingError(DistlibException): + """A required metadata is missing""" + + +class MetadataConflictError(DistlibException): + """Attempt to read or write metadata fields that are conflictual.""" + + +class MetadataUnrecognizedVersionError(DistlibException): + """Unknown metadata version number.""" + + +class MetadataInvalidError(DistlibException): + """A metadata value is invalid""" + +# 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.1' + +_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') + +_426_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', 'Private-Version', + 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension', + 'Provides-Extra') + +_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', + 'Setup-Requires-Dist', 'Extension') + +_ALL_FIELDS = set() +_ALL_FIELDS.update(_241_FIELDS) +_ALL_FIELDS.update(_314_FIELDS) +_ALL_FIELDS.update(_345_FIELDS) +_ALL_FIELDS.update(_426_FIELDS) + +EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''') + + +def _version2fieldlist(version): + if version == '1.0': + return _241_FIELDS + elif version == '1.1': + return _314_FIELDS + elif version == '1.2': + return _345_FIELDS + elif version == '2.0': + return _426_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 = [] + for key, value in fields.items(): + if value in ([], 'UNKNOWN', None): + continue + keys.append(key) + + possible_versions = ['1.0', '1.1', '1.2', '2.0'] + + # 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') + if key not in _426_FIELDS and '2.0' in possible_versions: + possible_versions.remove('2.0') + + # 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) + is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS) + if int(is_1_1) + int(is_1_2) + int(is_2_0) > 1: + raise MetadataConflictError('You used incompatible 1.1/1.2/2.0 fields') + + # we have the choice, 1.0, or 1.2, or 2.0 + # - 1.0 has a broken Summary field but works with all tools + # - 1.1 is to avoid + # - 1.2 fixes Summary but has little adoption + # - 2.0 adds more features and is very new + if not is_1_1 and not is_1_2 and not is_2_0: + # 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' + if is_1_2: + return '1.2' + + return '2.0' + +_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', + 'setup_requires_dist': 'Setup-Requires-Dist', + 'requires_python': 'Requires-Python', + 'requires_external': 'Requires-External', + 'requires': 'Requires', + 'provides': 'Provides', + 'obsoletes': 'Obsoletes', + 'project_url': 'Project-URL', + 'private_version': 'Private-Version', + 'obsoleted_by': 'Obsoleted-By', + 'extension': 'Extension', + 'provides_extra': 'Provides-Extra', +} + +_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', 'Setup-Requires-Dist', + 'Provides-Extra', 'Extension') +_LISTTUPLEFIELDS = ('Project-URL',) + +_ELEMENTSFIELD = ('Keywords',) + +_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') + +_MISSING = object() + +_FILESAFE = re.compile('[^A-Za-z0-9.]+') + + +def _get_name_and_version(name, version, for_filename=False): + """Return the distribution name with version. + + If for_filename is true, return a filename-escaped form.""" + if for_filename: + # 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) + + +class LegacyMetadata(object): + """The legacy 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 + - *scheme* is a version scheme name + """ + # TODO document the mapping API and UNKNOWN default key + + def __init__(self, path=None, fileobj=None, mapping=None, + scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._fields = {} + self.requires_files = [] + self._dependencies = None + self.scheme = scheme + 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) + self.set_metadata_version() + + def set_metadata_version(self): + self._fields['Metadata-Version'] = _best_version(self._fields) + + def _write_field(self, fileobj, name, value): + fileobj.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) + + 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 _remove_line_prefix(self, value): + return _LINE_PREFIX.sub('\n', value) + + def __getattr__(self, name): + if name in _ATTR2FIELD: + return self[name] + raise AttributeError(name) + + # + # Public API + # + +# dependencies = property(_get_dependencies, _set_dependencies) + + def get_fullname(self, filesafe=False): + """Return the distribution name with version. + + If filesafe is true, return a filename-escaped form.""" + return _get_name_and_version(self['Name'], self['Version'], filesafe) + + def is_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.""" + fp = codecs.open(filepath, 'r', encoding='utf-8') + try: + self.read_file(fp) + finally: + fp.close() + + 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'] + + # When reading, get all the fields we can + for field in _ALL_FIELDS: + if field not in msg: + continue + 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) + self.set_metadata_version() + + def write(self, filepath, skip_unknown=False): + """Write the metadata fields to filepath.""" + fp = codecs.open(filepath, 'w', encoding='utf-8') + try: + self.write_file(fp, skip_unknown) + finally: + fp.close() + + def write_file(self, fileobject, skip_unknown=False): + """Write the PKG-INFO format data to a file object.""" + self.set_metadata_version() + + for field in _version2fieldlist(self['Metadata-Version']): + values = self.get(field) + if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']): + continue + if field in _ELEMENTSFIELD: + self._write_field(fileobject, field, ','.join(values)) + continue + if field not in _LISTFIELDS: + if field == 'Description': + values = values.replace('\n', '\n |') + values = [values] + + if field in _LISTTUPLEFIELDS: + values = [','.join(value) for value in values] + + for value in values: + self._write_field(fileobject, field, value) + + def update(self, other=None, **kwargs): + """Set metadata values from the given iterable `other` and kwargs. + + Behavior is like `dict.update`: If `other` has a ``keys`` method, + they are looped over and ``self[key]`` is assigned ``other[key]``. + Else, ``other`` is an iterable of ``(key, value)`` iterables. + + Keys that don't match a metadata field or that have an empty value are + dropped. + """ + def _set(key, value): + if key in _ATTR2FIELD and value: + self.set(self._convert_name(key), value) + + if 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, string_types): + value = [v.strip() for v in value.split(',')] + else: + value = [] + elif (name in _LISTFIELDS and + not isinstance(value, (list, tuple))): + if isinstance(value, string_types): + value = [value] + else: + value = [] + + if logger.isEnabledFor(logging.WARNING): + project_name = self['Name'] + + scheme = get_scheme(self.scheme) + if name in _PREDICATE_FIELDS and value is not None: + for v in value: + # check that the values are valid + if not scheme.is_valid_matcher(v.split(';')[0]): + logger.warning( + '%r: %r is not valid (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 scheme.is_valid_constraint_list(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 scheme.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 + + 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: + 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: + value = self._fields[name] + if isinstance(value, string_types): + return value.split(',') + return self._fields[name] + + def check(self, strict=False): + """Check if the metadata is compliant. If strict is True then raise if + no Name or Version are provided""" + self.set_metadata_version() + + # 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) + + # checking metadata 1.2 (XXX needs to check 1.1, 1.0) + if self['Metadata-Version'] != '1.2': + return missing, warnings + + scheme = get_scheme(self.scheme) + + def are_valid_constraints(value): + for v in value: + if not scheme.is_valid_matcher(v.split(';')[0]): + return False + return True + + for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints), + (_VERSIONS_FIELDS, + scheme.is_valid_constraint_list), + (_VERSION_FIELDS, + scheme.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, skip_missing=False): + """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). + """ + self.set_metadata_version() + + mapping_1_0 = ( + ('metadata_version', 'Metadata-Version'), + ('name', 'Name'), + ('version', 'Version'), + ('summary', 'Summary'), + ('home_page', 'Home-page'), + ('author', 'Author'), + ('author_email', 'Author-email'), + ('license', 'License'), + ('description', 'Description'), + ('keywords', 'Keywords'), + ('platform', 'Platform'), + ('classifier', 'Classifier'), + ('download_url', 'Download-URL'), + ) + + data = {} + for key, field_name in mapping_1_0: + if not skip_missing or field_name in self._fields: + data[key] = self[field_name] + + if self['Metadata-Version'] == '1.2': + mapping_1_2 = ( + ('requires_dist', 'Requires-Dist'), + ('requires_python', 'Requires-Python'), + ('requires_external', 'Requires-External'), + ('provides_dist', 'Provides-Dist'), + ('obsoletes_dist', 'Obsoletes-Dist'), + ('project_url', 'Project-URL'), + ('maintainer', 'Maintainer'), + ('maintainer_email', 'Maintainer-email'), + ) + for key, field_name in mapping_1_2: + if not skip_missing or field_name in self._fields: + if key != 'project_url': + data[key] = self[field_name] + else: + data[key] = [','.join(u) for u in self[field_name]] + + elif self['Metadata-Version'] == '1.1': + mapping_1_1 = ( + ('provides', 'Provides'), + ('requires', 'Requires'), + ('obsoletes', 'Obsoletes'), + ) + for key, field_name in mapping_1_1: + if not skip_missing or field_name in self._fields: + data[key] = self[field_name] + + return data + + def add_requirements(self, requirements): + if self['Metadata-Version'] == '1.1': + # we can't have 1.1 metadata *and* Setuptools requires + for field in ('Obsoletes', 'Requires', 'Provides'): + if field in self: + del self[field] + self['Requires-Dist'] += requirements + + # Mapping API + # TODO could add iter* variants + + 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()] + + def __repr__(self): + return '<%s %s %s>' % (self.__class__.__name__, self.name, + self.version) + + +METADATA_FILENAME = 'pydist.json' + + +class Metadata(object): + """ + The metadata of a release. This implementation uses 2.0 (JSON) + metadata where possible. If not possible, it wraps a LegacyMetadata + instance which handles the key-value metadata format. + """ + + METADATA_VERSION_MATCHER = re.compile('^\d+(\.\d+)*$') + + NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I) + + VERSION_MATCHER = PEP426_VERSION_RE + + SUMMARY_MATCHER = re.compile('.{1,2047}') + + METADATA_VERSION = '2.0' + + GENERATOR = 'distlib (%s)' % __version__ + + MANDATORY_KEYS = { + 'name': (), + 'version': (), + 'summary': ('legacy',), + } + + INDEX_KEYS = ('name version license summary description author ' + 'author_email keywords platform home_page classifiers ' + 'download_url') + + DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires ' + 'dev_requires provides meta_requires obsoleted_by ' + 'supports_environments') + + SYNTAX_VALIDATORS = { + 'metadata_version': (METADATA_VERSION_MATCHER, ()), + 'name': (NAME_MATCHER, ('legacy',)), + 'version': (VERSION_MATCHER, ('legacy',)), + 'summary': (SUMMARY_MATCHER, ('legacy',)), + } + + __slots__ = ('_legacy', '_data', 'scheme') + + def __init__(self, path=None, fileobj=None, mapping=None, + scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._legacy = None + self._data = None + self.scheme = scheme + #import pdb; pdb.set_trace() + if mapping is not None: + try: + self._validate_mapping(mapping, scheme) + self._data = mapping + except MetadataUnrecognizedVersionError: + self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme) + self.validate() + else: + data = None + if path: + with open(path, 'rb') as f: + data = f.read() + elif fileobj: + data = fileobj.read() + if data is None: + # Initialised with no args - to be added + self._data = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + else: + if not isinstance(data, text_type): + data = data.decode('utf-8') + try: + self._data = json.loads(data) + self._validate_mapping(self._data, scheme) + except ValueError: + # Note: MetadataUnrecognizedVersionError does not + # inherit from ValueError (it's a DistlibException, + # which should not inherit from ValueError). + # The ValueError comes from the json.load - if that + # succeeds and we get a validation error, we want + # that to propagate + self._legacy = LegacyMetadata(fileobj=StringIO(data), + scheme=scheme) + self.validate() + + common_keys = set(('name', 'version', 'license', 'keywords', 'summary')) + + none_list = (None, list) + none_dict = (None, dict) + + mapped_keys = { + 'run_requires': ('Requires-Dist', list), + 'build_requires': ('Setup-Requires-Dist', list), + 'dev_requires': none_list, + 'test_requires': none_list, + 'meta_requires': none_list, + 'extras': ('Provides-Extra', list), + 'modules': none_list, + 'namespaces': none_list, + 'exports': none_dict, + 'commands': none_dict, + 'classifiers': ('Classifier', list), + 'source_url': ('Download-URL', None), + 'metadata_version': ('Metadata-Version', None), + } + + del none_list, none_dict + + def __getattribute__(self, key): + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, maker = mapped[key] + if self._legacy: + if lk is None: + result = None if maker is None else maker() + else: + result = self._legacy.get(lk) + else: + value = None if maker is None else maker() + if key not in ('commands', 'exports', 'modules', 'namespaces', + 'classifiers'): + result = self._data.get(key, value) + else: + # special cases for PEP 459 + sentinel = object() + result = sentinel + d = self._data.get('extensions') + if d: + if key == 'commands': + result = d.get('python.commands', value) + elif key == 'classifiers': + d = d.get('python.details') + if d: + result = d.get(key, value) + else: + d = d.get('python.exports') + if d: + result = d.get(key, value) + if result is sentinel: + result = value + elif key not in common: + result = object.__getattribute__(self, key) + elif self._legacy: + result = self._legacy.get(key) + else: + result = self._data.get(key) + return result + + def _validate_value(self, key, value, scheme=None): + if key in self.SYNTAX_VALIDATORS: + pattern, exclusions = self.SYNTAX_VALIDATORS[key] + if (scheme or self.scheme) not in exclusions: + m = pattern.match(value) + if not m: + raise MetadataInvalidError('%r is an invalid value for ' + 'the %r property' % (value, + key)) + + def __setattr__(self, key, value): + self._validate_value(key, value) + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, _ = mapped[key] + if self._legacy: + if lk is None: + raise NotImplementedError + self._legacy[lk] = value + elif key not in ('commands', 'exports', 'modules', 'namespaces', + 'classifiers'): + self._data[key] = value + else: + # special cases for PEP 459 + d = self._data.setdefault('extensions', {}) + if key == 'commands': + d['python.commands'] = value + elif key == 'classifiers': + d = d.setdefault('python.details', {}) + d[key] = value + else: + d = d.setdefault('python.exports', {}) + d[key] = value + elif key not in common: + object.__setattr__(self, key, value) + else: + if key == 'keywords': + if isinstance(value, string_types): + value = value.strip() + if value: + value = value.split() + else: + value = [] + if self._legacy: + self._legacy[key] = value + else: + self._data[key] = value + + @property + def name_and_version(self): + return _get_name_and_version(self.name, self.version, True) + + @property + def provides(self): + if self._legacy: + result = self._legacy['Provides-Dist'] + else: + result = self._data.setdefault('provides', []) + s = '%s (%s)' % (self.name, self.version) + if s not in result: + result.append(s) + return result + + @provides.setter + def provides(self, value): + if self._legacy: + self._legacy['Provides-Dist'] = value + else: + self._data['provides'] = value + + def get_requirements(self, reqts, extras=None, env=None): + """ + Base method to get dependencies, given a set of extras + to satisfy and an optional environment context. + :param reqts: A list of sometimes-wanted dependencies, + perhaps dependent on extras and environment. + :param extras: A list of optional components being requested. + :param env: An optional environment for marker evaluation. + """ + if self._legacy: + result = reqts + else: + result = [] + extras = get_extras(extras or [], self.extras) + for d in reqts: + if 'extra' not in d and 'environment' not in d: + # unconditional + include = True + else: + if 'extra' not in d: + # Not extra-dependent - only environment-dependent + include = True + else: + include = d.get('extra') in extras + if include: + # Not excluded because of extras, check environment + marker = d.get('environment') + if marker: + include = interpret(marker, env) + if include: + result.extend(d['requires']) + for key in ('build', 'dev', 'test'): + e = ':%s:' % key + if e in extras: + extras.remove(e) + # A recursive call, but it should terminate since 'test' + # has been removed from the extras + reqts = self._data.get('%s_requires' % key, []) + result.extend(self.get_requirements(reqts, extras=extras, + env=env)) + return result + + @property + def dictionary(self): + if self._legacy: + return self._from_legacy() + return self._data + + @property + def dependencies(self): + if self._legacy: + raise NotImplementedError + else: + return extract_by_key(self._data, self.DEPENDENCY_KEYS) + + @dependencies.setter + def dependencies(self, value): + if self._legacy: + raise NotImplementedError + else: + self._data.update(value) + + def _validate_mapping(self, mapping, scheme): + if mapping.get('metadata_version') != self.METADATA_VERSION: + raise MetadataUnrecognizedVersionError() + missing = [] + for key, exclusions in self.MANDATORY_KEYS.items(): + if key not in mapping: + if scheme not in exclusions: + missing.append(key) + if missing: + msg = 'Missing metadata items: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + for k, v in mapping.items(): + self._validate_value(k, v, scheme) + + def validate(self): + if self._legacy: + missing, warnings = self._legacy.check(True) + if missing or warnings: + logger.warning('Metadata: missing: %s, warnings: %s', + missing, warnings) + else: + self._validate_mapping(self._data, self.scheme) + + def todict(self): + if self._legacy: + return self._legacy.todict(True) + else: + result = extract_by_key(self._data, self.INDEX_KEYS) + return result + + def _from_legacy(self): + assert self._legacy and not self._data + result = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + lmd = self._legacy.todict(True) # skip missing ones + for k in ('name', 'version', 'license', 'summary', 'description', + 'classifier'): + if k in lmd: + if k == 'classifier': + nk = 'classifiers' + else: + nk = k + result[nk] = lmd[k] + kw = lmd.get('Keywords', []) + if kw == ['']: + kw = [] + result['keywords'] = kw + keys = (('requires_dist', 'run_requires'), + ('setup_requires_dist', 'build_requires')) + for ok, nk in keys: + if ok in lmd and lmd[ok]: + result[nk] = [{'requires': lmd[ok]}] + result['provides'] = self.provides + author = {} + maintainer = {} + return result + + LEGACY_MAPPING = { + 'name': 'Name', + 'version': 'Version', + 'license': 'License', + 'summary': 'Summary', + 'description': 'Description', + 'classifiers': 'Classifier', + } + + def _to_legacy(self): + def process_entries(entries): + reqts = set() + for e in entries: + extra = e.get('extra') + env = e.get('environment') + rlist = e['requires'] + for r in rlist: + if not env and not extra: + reqts.add(r) + else: + marker = '' + if extra: + marker = 'extra == "%s"' % extra + if env: + if marker: + marker = '(%s) and %s' % (env, marker) + else: + marker = env + reqts.add(';'.join((r, marker))) + return reqts + + assert self._data and not self._legacy + result = LegacyMetadata() + nmd = self._data + for nk, ok in self.LEGACY_MAPPING.items(): + if nk in nmd: + result[ok] = nmd[nk] + r1 = process_entries(self.run_requires + self.meta_requires) + r2 = process_entries(self.build_requires + self.dev_requires) + if self.extras: + result['Provides-Extra'] = sorted(self.extras) + result['Requires-Dist'] = sorted(r1) + result['Setup-Requires-Dist'] = sorted(r2) + # TODO: other fields such as contacts + return result + + def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): + if [path, fileobj].count(None) != 1: + raise ValueError('Exactly one of path and fileobj is needed') + self.validate() + if legacy: + if self._legacy: + legacy_md = self._legacy + else: + legacy_md = self._to_legacy() + if path: + legacy_md.write(path, skip_unknown=skip_unknown) + else: + legacy_md.write_file(fileobj, skip_unknown=skip_unknown) + else: + if self._legacy: + d = self._from_legacy() + else: + d = self._data + if fileobj: + json.dump(d, fileobj, ensure_ascii=True, indent=2, + sort_keys=True) + else: + with codecs.open(path, 'w', 'utf-8') as f: + json.dump(d, f, ensure_ascii=True, indent=2, + sort_keys=True) + + def add_requirements(self, requirements): + if self._legacy: + self._legacy.add_requirements(requirements) + else: + run_requires = self._data.setdefault('run_requires', []) + always = None + for entry in run_requires: + if 'environment' not in entry and 'extra' not in entry: + always = entry + break + if always is None: + always = { 'requires': requirements } + run_requires.insert(0, always) + else: + rset = set(always['requires']) | set(requirements) + always['requires'] = sorted(rset) + + def __repr__(self): + name = self.name or '(no name)' + version = self.version or 'no version' + return '<%s %s %s (%s)>' % (self.__class__.__name__, + self.metadata_version, name, version) diff --git a/src/build_utils/distlib/resources.py b/src/build_utils/distlib/resources.py new file mode 100644 index 0000000000..d24c0e9379 --- /dev/null +++ b/src/build_utils/distlib/resources.py @@ -0,0 +1,323 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import unicode_literals + +import bisect +import io +import logging +import os +import pkgutil +import shutil +import sys +import types +import zipimport + +from . import DistlibException +from .util import cached_property, get_cache_base, path_to_cache_dir, Cache + +logger = logging.getLogger(__name__) + + +cache = None # created when needed + + +class ResourceCache(Cache): + def __init__(self, base=None): + if base is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('resource-cache')) + super(ResourceCache, self).__init__(base) + + def is_stale(self, resource, path): + """ + Is the cache stale for the given resource? + + :param resource: The :class:`Resource` being cached. + :param path: The path of the resource in the cache. + :return: True if the cache is stale. + """ + # Cache invalidation is a hard problem :-) + return True + + def get(self, resource): + """ + Get a resource into the cache, + + :param resource: A :class:`Resource` instance. + :return: The pathname of the resource in the cache. + """ + prefix, path = resource.finder.get_cache_info(resource) + if prefix is None: + result = path + else: + result = os.path.join(self.base, self.prefix_to_dir(prefix), path) + dirname = os.path.dirname(result) + if not os.path.isdir(dirname): + os.makedirs(dirname) + if not os.path.exists(result): + stale = True + else: + stale = self.is_stale(resource, path) + if stale: + # write the bytes of the resource to the cache location + with open(result, 'wb') as f: + f.write(resource.bytes) + return result + + +class ResourceBase(object): + def __init__(self, finder, name): + self.finder = finder + self.name = name + + +class Resource(ResourceBase): + """ + A class representing an in-package resource, such as a data file. This is + not normally instantiated by user code, but rather by a + :class:`ResourceFinder` which manages the resource. + """ + is_container = False # Backwards compatibility + + def as_stream(self): + """ + Get the resource as a stream. + + This is not a property to make it obvious that it returns a new stream + each time. + """ + return self.finder.get_stream(self) + + @cached_property + def file_path(self): + global cache + if cache is None: + cache = ResourceCache() + return cache.get(self) + + @cached_property + def bytes(self): + return self.finder.get_bytes(self) + + @cached_property + def size(self): + return self.finder.get_size(self) + + +class ResourceContainer(ResourceBase): + is_container = True # Backwards compatibility + + @cached_property + def resources(self): + return self.finder.get_resources(self) + + +class ResourceFinder(object): + """ + Resource finder for file system resources. + """ + def __init__(self, module): + self.module = module + self.loader = getattr(module, '__loader__', None) + self.base = os.path.dirname(getattr(module, '__file__', '')) + + def _adjust_path(self, path): + return os.path.realpath(path) + + def _make_path(self, resource_name): + # Issue #50: need to preserve type of path on Python 2.x + # like os.path._get_sep + if isinstance(resource_name, bytes): # should only happen on 2.x + sep = b'/' + else: + sep = '/' + parts = resource_name.split(sep) + parts.insert(0, self.base) + result = os.path.join(*parts) + return self._adjust_path(result) + + def _find(self, path): + return os.path.exists(path) + + def get_cache_info(self, resource): + return None, resource.path + + def find(self, resource_name): + path = self._make_path(resource_name) + if not self._find(path): + result = None + else: + if self._is_directory(path): + result = ResourceContainer(self, resource_name) + else: + result = Resource(self, resource_name) + result.path = path + return result + + def get_stream(self, resource): + return open(resource.path, 'rb') + + def get_bytes(self, resource): + with open(resource.path, 'rb') as f: + return f.read() + + def get_size(self, resource): + return os.path.getsize(resource.path) + + def get_resources(self, resource): + def allowed(f): + return f != '__pycache__' and not f.endswith(('.pyc', '.pyo')) + return set([f for f in os.listdir(resource.path) if allowed(f)]) + + def is_container(self, resource): + return self._is_directory(resource.path) + + _is_directory = staticmethod(os.path.isdir) + + +class ZipResourceFinder(ResourceFinder): + """ + Resource finder for resources in .zip files. + """ + def __init__(self, module): + super(ZipResourceFinder, self).__init__(module) + archive = self.loader.archive + self.prefix_len = 1 + len(archive) + # PyPy doesn't have a _files attr on zipimporter, and you can't set one + if hasattr(self.loader, '_files'): + self._files = self.loader._files + else: + self._files = zipimport._zip_directory_cache[archive] + self.index = sorted(self._files) + + def _adjust_path(self, path): + return path + + def _find(self, path): + path = path[self.prefix_len:] + if path in self._files: + result = True + else: + if path and path[-1] != os.sep: + path = path + os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + if not result: + logger.debug('_find failed: %r %r', path, self.loader.prefix) + else: + logger.debug('_find worked: %r %r', path, self.loader.prefix) + return result + + def get_cache_info(self, resource): + prefix = self.loader.archive + path = resource.path[1 + len(prefix):] + return prefix, path + + def get_bytes(self, resource): + return self.loader.get_data(resource.path) + + def get_stream(self, resource): + return io.BytesIO(self.get_bytes(resource)) + + def get_size(self, resource): + path = resource.path[self.prefix_len:] + return self._files[path][3] + + def get_resources(self, resource): + path = resource.path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + plen = len(path) + result = set() + i = bisect.bisect(self.index, path) + while i < len(self.index): + if not self.index[i].startswith(path): + break + s = self.index[i][plen:] + result.add(s.split(os.sep, 1)[0]) # only immediate children + i += 1 + return result + + def _is_directory(self, path): + path = path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + return result + +_finder_registry = { + type(None): ResourceFinder, + zipimport.zipimporter: ZipResourceFinder +} + +try: + import _frozen_importlib + _finder_registry[_frozen_importlib.SourceFileLoader] = ResourceFinder + _finder_registry[_frozen_importlib.FileFinder] = ResourceFinder +except (ImportError, AttributeError): + pass + + +def register_finder(loader, finder_maker): + _finder_registry[type(loader)] = finder_maker + +_finder_cache = {} + + +def finder(package): + """ + Return a resource finder for a package. + :param package: The name of the package. + :return: A :class:`ResourceFinder` instance for the package. + """ + if package in _finder_cache: + result = _finder_cache[package] + else: + if package not in sys.modules: + __import__(package) + module = sys.modules[package] + path = getattr(module, '__path__', None) + if path is None: + raise DistlibException('You cannot get a finder for a module, ' + 'only for a package') + loader = getattr(module, '__loader__', None) + finder_maker = _finder_registry.get(type(loader)) + if finder_maker is None: + raise DistlibException('Unable to locate finder for %r' % package) + result = finder_maker(module) + _finder_cache[package] = result + return result + + +_dummy_module = types.ModuleType(str('__dummy__')) + + +def finder_for_path(path): + """ + Return a resource finder for a path, which should represent a container. + + :param path: The path. + :return: A :class:`ResourceFinder` instance for the path. + """ + result = None + # calls any path hooks, gets importer into cache + pkgutil.get_importer(path) + loader = sys.path_importer_cache.get(path) + finder = _finder_registry.get(type(loader)) + if finder: + module = _dummy_module + module.__file__ = os.path.join(path, '') + module.__loader__ = loader + result = finder(module) + return result diff --git a/src/build_utils/distlib/scripts.py b/src/build_utils/distlib/scripts.py new file mode 100644 index 0000000000..d212f047d0 --- /dev/null +++ b/src/build_utils/distlib/scripts.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from io import BytesIO +import logging +import os +import re +import struct +import sys + +from .compat import sysconfig, detect_encoding, ZipFile +from .resources import finder +from .util import (FileOperator, get_export_entry, convert_path, + get_executable, in_venv) + +logger = logging.getLogger(__name__) + +_DEFAULT_MANIFEST = ''' + + + + + + + + + + + + +'''.strip() + +# check if Python is called on the first line with this expression +FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$') +SCRIPT_TEMPLATE = '''# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, re + + def _resolve(module, func): + __import__(module) + mod = sys.modules[module] + parts = func.split('.') + result = getattr(mod, parts.pop(0)) + for p in parts: + result = getattr(result, p) + return result + + try: + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + + func = _resolve('%(module)s', '%(func)s') + rc = func() # None interpreted as 0 + except Exception as e: # only supporting Python >= 2.6 + sys.stderr.write('%%s\\n' %% e) + rc = 1 + sys.exit(rc) +''' + + +class ScriptMaker(object): + """ + A class to copy or create scripts from source scripts or callable + specifications. + """ + script_template = SCRIPT_TEMPLATE + + executable = None # for shebangs + + def __init__(self, source_dir, target_dir, add_launchers=True, + dry_run=False, fileop=None): + self.source_dir = source_dir + self.target_dir = target_dir + self.add_launchers = add_launchers + self.force = False + self.clobber = False + # It only makes sense to set mode bits on POSIX. + self.set_mode = (os.name == 'posix') + self.variants = set(('', 'X.Y')) + self._fileop = fileop or FileOperator(dry_run) + + def _get_alternate_executable(self, executable, options): + if options.get('gui', False) and os.name == 'nt': + dn, fn = os.path.split(executable) + fn = fn.replace('python', 'pythonw') + executable = os.path.join(dn, fn) + return executable + + def _get_shebang(self, encoding, post_interp=b'', options=None): + enquote = True + if self.executable: + executable = self.executable + enquote = False # assume this will be taken care of + elif not sysconfig.is_python_build(): + executable = get_executable() + elif in_venv(): + executable = os.path.join(sysconfig.get_path('scripts'), + 'python%s' % sysconfig.get_config_var('EXE')) + else: + executable = os.path.join( + sysconfig.get_config_var('BINDIR'), + 'python%s%s' % (sysconfig.get_config_var('VERSION'), + sysconfig.get_config_var('EXE'))) + if options: + executable = self._get_alternate_executable(executable, options) + + # If the user didn't specify an executable, it may be necessary to + # cater for executable paths with spaces (not uncommon on Windows) + if enquote and ' ' in executable: + executable = '"%s"' % executable + # Issue #51: don't use fsencode, since we later try to + # check that the shebang is decodable using utf-8. + executable = executable.encode('utf-8') + # in case of IronPython, play safe and enable frames support + if (sys.platform == 'cli' and '-X:Frames' not in post_interp + and '-X:FullFrames' not in post_interp): + post_interp += b' -X:Frames' + 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' % 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. + if encoding != 'utf-8': + try: + shebang.decode(encoding) + except UnicodeDecodeError: + raise ValueError( + 'The shebang (%r) is not decodable ' + 'from the script encoding (%r)' % (shebang, encoding)) + return shebang + + def _get_script_text(self, entry): + return self.script_template % dict(module=entry.prefix, + func=entry.suffix) + + manifest = _DEFAULT_MANIFEST + + def get_manifest(self, exename): + base = os.path.basename(exename) + return self.manifest % base + + def _write_script(self, names, shebang, script_bytes, filenames, ext): + use_launcher = self.add_launchers and os.name == 'nt' + linesep = os.linesep.encode('utf-8') + if not use_launcher: + script_bytes = shebang + linesep + script_bytes + else: + if ext == 'py': + launcher = self._get_launcher('t') + else: + launcher = self._get_launcher('w') + stream = BytesIO() + with ZipFile(stream, 'w') as zf: + zf.writestr('__main__.py', script_bytes) + zip_data = stream.getvalue() + script_bytes = launcher + shebang + linesep + zip_data + for name in names: + outname = os.path.join(self.target_dir, name) + if use_launcher: + n, e = os.path.splitext(outname) + if e.startswith('.py'): + outname = n + outname = '%s.exe' % outname + try: + self._fileop.write_binary_file(outname, script_bytes) + except Exception: + # Failed writing an executable - it might be in use. + logger.warning('Failed to write executable - trying to ' + 'use .deleteme logic') + dfname = '%s.deleteme' % outname + if os.path.exists(dfname): + os.remove(dfname) # Not allowed to fail here + os.rename(outname, dfname) # nor here + self._fileop.write_binary_file(outname, script_bytes) + logger.debug('Able to replace executable using ' + '.deleteme logic') + try: + os.remove(dfname) + except Exception: + pass # still in use - ignore error + else: + if os.name == 'nt' and not outname.endswith('.' + ext): + outname = '%s.%s' % (outname, ext) + if os.path.exists(outname) and not self.clobber: + logger.warning('Skipping existing file %s', outname) + continue + self._fileop.write_binary_file(outname, script_bytes) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + + def _make_script(self, entry, filenames, options=None): + post_interp = b'' + if options: + args = options.get('interpreter_args', []) + if args: + args = ' %s' % ' '.join(args) + post_interp = args.encode('utf-8') + shebang = self._get_shebang('utf-8', post_interp, options=options) + script = self._get_script_text(entry).encode('utf-8') + name = entry.name + scriptnames = set() + if '' in self.variants: + scriptnames.add(name) + if 'X' in self.variants: + scriptnames.add('%s%s' % (name, sys.version[0])) + if 'X.Y' in self.variants: + scriptnames.add('%s-%s' % (name, sys.version[:3])) + if options and options.get('gui', False): + ext = 'pyw' + else: + ext = 'py' + self._write_script(scriptnames, shebang, script, filenames, ext) + + def _copy_script(self, script, filenames): + adjust = False + script = os.path.join(self.source_dir, convert_path(script)) + outname = os.path.join(self.target_dir, os.path.basename(script)) + if not self.force and not self._fileop.newer(script, outname): + logger.debug('not copying %s (up-to-date)', script) + return + + # 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) + return + + match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) + if match: + adjust = True + post_interp = match.group(1) or b'' + + if not adjust: + if f: + f.close() + self._fileop.copy_file(script, outname) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + else: + logger.info('copying and adjusting %s -> %s', script, + self.target_dir) + if not self._fileop.dry_run: + shebang = self._get_shebang(encoding, post_interp) + if b'pythonw' in first_line: + ext = 'pyw' + else: + ext = 'py' + n = os.path.basename(outname) + self._write_script([n], shebang, f.read(), filenames, ext) + if f: + f.close() + + @property + def dry_run(self): + return self._fileop.dry_run + + @dry_run.setter + def dry_run(self, value): + self._fileop.dry_run = value + + if os.name == 'nt': + # Executable launcher support. + # Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/ + + def _get_launcher(self, kind): + if struct.calcsize('P') == 8: # 64-bit + bits = '64' + else: + bits = '32' + name = '%s%s.exe' % (kind, bits) + # Issue 31: don't hardcode an absolute package name, but + # determine it relative to the current package + distlib_package = __name__.rsplit('.', 1)[0] + result = finder(distlib_package).find(name).bytes + return result + + # Public API follows + + def make(self, specification, options=None): + """ + Make a script. + + :param specification: The specification, which is either a valid export + entry specification (to make a script from a + callable) or a filename (to make a script by + copying from a source location). + :param options: A dictionary of options controlling script generation. + :return: A list of all absolute pathnames written to. + """ + filenames = [] + entry = get_export_entry(specification) + if entry is None: + self._copy_script(specification, filenames) + else: + self._make_script(entry, filenames, options=options) + return filenames + + def make_multiple(self, specifications, options=None): + """ + Take a list of specifications and make scripts from them, + :param specifications: A list of specifications. + :return: A list of all absolute pathnames written to, + """ + filenames = [] + for specification in specifications: + filenames.extend(self.make(specification, options)) + return filenames diff --git a/src/build_utils/distlib/t32.exe b/src/build_utils/distlib/t32.exe new file mode 100644 index 0000000000000000000000000000000000000000..bdc9d1ec895b5e2e5bce66c7e9941ee9b7523edd GIT binary patch literal 91648 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 PLuc=&`yBZH;=q3a9Wlw0 literal 0 HcmV?d00001 diff --git a/src/build_utils/distlib/util.py b/src/build_utils/distlib/util.py new file mode 100644 index 0000000000..425606e7cd --- /dev/null +++ b/src/build_utils/distlib/util.py @@ -0,0 +1,1575 @@ +# +# Copyright (C) 2012-2013 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import codecs +from collections import deque +import contextlib +import csv +from glob import iglob as std_iglob +import io +import json +import logging +import os +import py_compile +import re +import shutil +import socket +import ssl +import subprocess +import sys +import tarfile +import tempfile +try: + import threading +except ImportError: + import dummy_threading as threading +import time + +from . import DistlibException +from .compat import (string_types, text_type, shutil, raw_input, StringIO, + cache_from_source, urlopen, httplib, xmlrpclib, splittype, + HTTPHandler, HTTPSHandler as BaseHTTPSHandler, + BaseConfigurator, valid_ident, Container, configparser, + URLError, match_hostname, CertificateError, ZipFile) + +logger = logging.getLogger(__name__) + +# +# Requirement parsing code for name + optional constraints + optional extras +# +# e.g. 'foo >= 1.2, < 2.0 [bar, baz]' +# +# The regex can seem a bit hairy, so we build it up out of smaller pieces +# which are manageable. +# + +COMMA = r'\s*,\s*' +COMMA_RE = re.compile(COMMA) + +IDENT = r'(\w|[.-])+' +EXTRA_IDENT = r'(\*|:(\*|\w+):|' + IDENT + ')' +VERSPEC = IDENT + r'\*?' + +RELOP = '([<>=!~]=)|[<>]' + +# +# The first relop is optional - if absent, will be taken as '~=' +# +BARE_CONSTRAINTS = ('(' + RELOP + r')?\s*(' + VERSPEC + ')(' + COMMA + '(' + + RELOP + r')\s*(' + VERSPEC + '))*') + +DIRECT_REF = '(from\s+(?P.*))' + +# +# Either the bare constraints or the bare constraints in parentheses +# +CONSTRAINTS = (r'\(\s*(?P' + BARE_CONSTRAINTS + '|' + DIRECT_REF + + r')\s*\)|(?P' + BARE_CONSTRAINTS + '\s*)') + +EXTRA_LIST = EXTRA_IDENT + '(' + COMMA + EXTRA_IDENT + ')*' +EXTRAS = r'\[\s*(?P' + EXTRA_LIST + r')?\s*\]' +REQUIREMENT = ('(?P' + IDENT + r')\s*(' + EXTRAS + r'\s*)?(\s*' + + CONSTRAINTS + ')?$') +REQUIREMENT_RE = re.compile(REQUIREMENT) + +# +# Used to scan through the constraints +# +RELOP_IDENT = '(?P' + RELOP + r')\s*(?P' + VERSPEC + ')' +RELOP_IDENT_RE = re.compile(RELOP_IDENT) + +def parse_requirement(s): + + def get_constraint(m): + d = m.groupdict() + return d['op'], d['vn'] + + result = None + m = REQUIREMENT_RE.match(s) + if m: + d = m.groupdict() + name = d['dn'] + cons = d['c1'] or d['c2'] + if not d['diref']: + url = None + else: + # direct reference + cons = None + url = d['diref'].strip() + if not cons: + cons = None + constr = '' + rs = d['dn'] + else: + if cons[0] not in '<>!=': + cons = '~=' + cons + iterator = RELOP_IDENT_RE.finditer(cons) + cons = [get_constraint(m) for m in iterator] + rs = '%s (%s)' % (name, ', '.join(['%s %s' % con for con in cons])) + if not d['ex']: + extras = None + else: + extras = COMMA_RE.split(d['ex']) + result = Container(name=name, constraints=cons, extras=extras, + requirement=rs, source=s, url=url) + return result + + +def get_resources_dests(resources_root, rules): + """Find destinations for resources files""" + + def get_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('/') + + + 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 = get_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 = get_rel_path(abs_base, abs_path) + rel_dest = dest.replace(os.path.sep, '/').rstrip('/') + destinations[resource_file] = rel_dest + '/' + rel_path + return destinations + + +def in_venv(): + if hasattr(sys, 'real_prefix'): + # virtualenv venvs + result = True + else: + # PEP 405 venvs + result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + return result + + +def get_executable(): + if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__' + in os.environ): + result = os.environ['__PYVENV_LAUNCHER__'] + else: + result = sys.executable + return result + + +def proceed(prompt, allowed_chars, error_prompt=None, default=None): + p = prompt + while True: + s = raw_input(p) + p = prompt + if not s and default: + s = default + if s: + c = s[0].lower() + if c in allowed_chars: + break + if error_prompt: + p = '%c: %s\n%s' % (c, error_prompt, prompt) + return c + + +def extract_by_key(d, keys): + if isinstance(keys, string_types): + keys = keys.split() + result = {} + for key in keys: + if key in d: + result[key] = d[key] + return result + +def read_exports(stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + # Try to load as JSON, falling back on legacy format + data = stream.read() + stream = StringIO(data) + try: + data = json.load(stream) + result = data['extensions']['python.exports']['exports'] + for group, entries in result.items(): + for k, v in entries.items(): + s = '%s = %s' % (k, v) + entry = get_export_entry(s) + assert entry is not None + entries[k] = entry + return result + except Exception: + stream.seek(0, 0) + cp = configparser.ConfigParser() + if hasattr(cp, 'read_file'): + cp.read_file(stream) + else: + cp.readfp(stream) + result = {} + for key in cp.sections(): + result[key] = entries = {} + for name, value in cp.items(key): + s = '%s = %s' % (name, value) + entry = get_export_entry(s) + assert entry is not None + #entry.dist = self + entries[name] = entry + return result + + +def write_exports(exports, stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getwriter('utf-8')(stream) + cp = configparser.ConfigParser() + for k, v in exports.items(): + # TODO check k, v for valid values + cp.add_section(k) + for entry in v.values(): + if entry.suffix is None: + s = entry.prefix + else: + s = '%s:%s' % (entry.prefix, entry.suffix) + if entry.flags: + s = '%s [%s]' % (s, ', '.join(entry.flags)) + cp.set(k, entry.name, s) + cp.write(stream) + + +@contextlib.contextmanager +def tempdir(): + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + +@contextlib.contextmanager +def chdir(d): + cwd = os.getcwd() + try: + os.chdir(d) + yield + finally: + os.chdir(cwd) + + +@contextlib.contextmanager +def socket_timeout(seconds=15): + cto = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(seconds) + yield + finally: + socket.setdefaulttimeout(cto) + + +class cached_property(object): + def __init__(self, func): + self.func = func + #for attr in ('__name__', '__module__', '__doc__'): + # setattr(self, attr, getattr(func, attr, None)) + + def __get__(self, obj, cls=None): + if obj is None: + return self + value = self.func(obj) + object.__setattr__(obj, self.func.__name__, value) + #obj.__dict__[self.func.__name__] = value = self.func(obj) + return value + +def convert_path(pathname): + """Return 'pathname' as a name that will work on the native filesystem. + + The path is split on '/' and put 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 + ValueError on non-Unix-ish systems if 'pathname' either starts or + ends with a slash. + """ + if os.sep == '/': + return pathname + if not pathname: + return pathname + if pathname[0] == '/': + raise ValueError("path '%s' cannot be absolute" % pathname) + if pathname[-1] == '/': + raise ValueError("path '%s' cannot end with '/'" % pathname) + + paths = pathname.split('/') + while os.curdir in paths: + paths.remove(os.curdir) + if not paths: + return os.curdir + return os.path.join(*paths) + + +class FileOperator(object): + def __init__(self, dry_run=False): + self.dry_run = dry_run + self.ensured = set() + self._init_record() + + def _init_record(self): + self.record = False + self.files_written = set() + self.dirs_created = set() + + def record_as_written(self, path): + if self.record: + self.files_written.add(path) + + def newer(self, source, target): + """Tell if the target is newer than the source. + + Returns true if 'source' exists and is more recently modified than + 'target', or if 'source' exists and 'target' doesn't. + + Returns false if both exist and 'target' is the same age or younger + than 'source'. Raise PackagingFileError if 'source' does not exist. + + Note that this test is not very accurate: files created in the same + second will have the same "age". + """ + if not os.path.exists(source): + raise DistlibException("file '%r' does not exist" % + os.path.abspath(source)) + if not os.path.exists(target): + return True + + return os.stat(source).st_mtime > os.stat(target).st_mtime + + def copy_file(self, infile, outfile, check=True): + """Copy a file respecting dry-run and force flags. + """ + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying %s to %s', infile, outfile) + if not self.dry_run: + msg = None + if check: + if os.path.islink(outfile): + msg = '%s is a symlink' % outfile + elif os.path.exists(outfile) and not os.path.isfile(outfile): + msg = '%s is a non-regular file' % outfile + if msg: + raise ValueError(msg + ' which would be overwritten') + shutil.copyfile(infile, outfile) + self.record_as_written(outfile) + + def copy_stream(self, instream, outfile, encoding=None): + assert not os.path.isdir(outfile) + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying stream %s to %s', instream, outfile) + if not self.dry_run: + if encoding is None: + outstream = open(outfile, 'wb') + else: + outstream = codecs.open(outfile, 'w', encoding=encoding) + try: + shutil.copyfileobj(instream, outstream) + finally: + outstream.close() + self.record_as_written(outfile) + + def write_binary_file(self, path, data): + self.ensure_dir(os.path.dirname(path)) + if not self.dry_run: + with open(path, 'wb') as f: + f.write(data) + self.record_as_written(path) + + def write_text_file(self, path, data, encoding): + self.ensure_dir(os.path.dirname(path)) + if not self.dry_run: + with open(path, 'wb') as f: + f.write(data.encode(encoding)) + self.record_as_written(path) + + def set_mode(self, bits, mask, files): + if os.name == 'posix': + # Set the executable bits (owner, group, and world) on + # all the files specified. + for f in files: + if self.dry_run: + logger.info("changing mode of %s", f) + else: + mode = (os.stat(f).st_mode | bits) & mask + logger.info("changing mode of %s to %o", f, mode) + os.chmod(f, mode) + + set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f) + + def ensure_dir(self, path): + path = os.path.abspath(path) + if path not in self.ensured and not os.path.exists(path): + self.ensured.add(path) + d, f = os.path.split(path) + self.ensure_dir(d) + logger.info('Creating %s' % path) + if not self.dry_run: + os.mkdir(path) + if self.record: + self.dirs_created.add(path) + + def byte_compile(self, path, optimize=False, force=False, prefix=None): + dpath = cache_from_source(path, not optimize) + logger.info('Byte-compiling %s to %s', path, dpath) + if not self.dry_run: + if force or self.newer(path, dpath): + if not prefix: + diagpath = None + else: + assert path.startswith(prefix) + diagpath = path[len(prefix):] + py_compile.compile(path, dpath, diagpath, True) # raise error + self.record_as_written(dpath) + return dpath + + def ensure_removed(self, path): + if os.path.exists(path): + if os.path.isdir(path) and not os.path.islink(path): + logger.debug('Removing directory tree at %s', path) + if not self.dry_run: + shutil.rmtree(path) + if self.record: + if path in self.dirs_created: + self.dirs_created.remove(path) + else: + if os.path.islink(path): + s = 'link' + else: + s = 'file' + logger.debug('Removing %s %s', s, path) + if not self.dry_run: + os.remove(path) + if self.record: + if path in self.files_written: + self.files_written.remove(path) + + def is_writable(self, path): + result = False + while not result: + if os.path.exists(path): + result = os.access(path, os.W_OK) + break + parent = os.path.dirname(path) + if parent == path: + break + path = parent + return result + + def commit(self): + """ + Commit recorded changes, turn off recording, return + changes. + """ + assert self.record + result = self.files_written, self.dirs_created + self._init_record() + return result + + def rollback(self): + if not self.dry_run: + for f in list(self.files_written): + if os.path.exists(f): + os.remove(f) + # dirs should all be empty now, except perhaps for + # __pycache__ subdirs + # reverse so that subdirs appear before their parents + dirs = sorted(self.dirs_created, reverse=True) + for d in dirs: + flist = os.listdir(d) + if flist: + assert flist == ['__pycache__'] + sd = os.path.join(d, flist[0]) + os.rmdir(sd) + os.rmdir(d) # should fail if non-empty + self._init_record() + +def resolve(module_name, dotted_path): + if module_name in sys.modules: + mod = sys.modules[module_name] + else: + mod = __import__(module_name) + if dotted_path is None: + result = mod + else: + parts = dotted_path.split('.') + result = getattr(mod, parts.pop(0)) + for p in parts: + result = getattr(result, p) + return result + + +class ExportEntry(object): + def __init__(self, name, prefix, suffix, flags): + self.name = name + self.prefix = prefix + self.suffix = suffix + self.flags = flags + + @cached_property + def value(self): + return resolve(self.prefix, self.suffix) + + def __repr__(self): + return '' % (self.name, self.prefix, + self.suffix, self.flags) + + def __eq__(self, other): + if not isinstance(other, ExportEntry): + result = False + else: + result = (self.name == other.name and + self.prefix == other.prefix and + self.suffix == other.suffix and + self.flags == other.flags) + return result + + __hash__ = object.__hash__ + + +ENTRY_RE = re.compile(r'''(?P(\w|[-.])+) + \s*=\s*(?P(\w+)([:\.]\w+)*) + \s*(\[\s*(?P\w+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? + ''', re.VERBOSE) + + +def get_export_entry(specification): + m = ENTRY_RE.search(specification) + if not m: + result = None + if '[' in specification or ']' in specification: + raise DistlibException('Invalid specification ' + '%r' % specification) + else: + d = m.groupdict() + name = d['name'] + path = d['callable'] + colons = path.count(':') + if colons == 0: + prefix, suffix = path, None + else: + if colons != 1: + raise DistlibException('Invalid specification ' + '%r' % specification) + prefix, suffix = path.split(':') + flags = d['flags'] + if flags is None: + if '[' in specification or ']' in specification: + raise DistlibException('Invalid specification ' + '%r' % specification) + flags = [] + else: + flags = [f.strip() for f in flags.split(',')] + result = ExportEntry(name, prefix, suffix, flags) + return result + + +def get_cache_base(suffix=None): + """ + Return the default base location for distlib caches. If the directory does + not exist, it is created. Use the suffix provided for the base directory, + and default to '.distlib' if it isn't provided. + + On Windows, if LOCALAPPDATA is defined in the environment, then it is + assumed to be a directory, and will be the parent directory of the result. + On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home + directory - using os.expanduser('~') - will be the parent directory of + the result. + + The result is just the directory '.distlib' in the parent directory as + determined above, or with the name specified with ``suffix``. + """ + if suffix is None: + suffix = '.distlib' + if os.name == 'nt' and 'LOCALAPPDATA' in os.environ: + result = os.path.expandvars('$localappdata') + else: + # Assume posix, or old Windows + result = os.path.expanduser('~') + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if os.path.isdir(result): + usable = os.access(result, os.W_OK) + if not usable: + logger.warning('Directory exists but is not writable: %s', result) + else: + try: + os.makedirs(result) + usable = True + except OSError: + logger.warning('Unable to create %s', result, exc_info=True) + usable = False + if not usable: + result = tempfile.mkdtemp() + logger.warning('Default location unusable, using %s', result) + return os.path.join(result, suffix) + + +def path_to_cache_dir(path): + """ + Convert an absolute path to a directory name for use in a cache. + + The algorithm used is: + + #. On Windows, any ``':'`` in the drive is replaced with ``'---'``. + #. Any occurrence of ``os.sep`` is replaced with ``'--'``. + #. ``'.cache'`` is appended. + """ + d, p = os.path.splitdrive(os.path.abspath(path)) + if d: + d = d.replace(':', '---') + p = p.replace(os.sep, '--') + return d + p + '.cache' + + +def ensure_slash(s): + if not s.endswith('/'): + return s + '/' + return s + + +def parse_credentials(netloc): + username = password = None + if '@' in netloc: + prefix, netloc = netloc.split('@', 1) + if ':' not in prefix: + username = prefix + else: + username, password = prefix.split(':', 1) + return username, password, netloc + + +def get_process_umask(): + result = os.umask(0o22) + os.umask(result) + return result + +def is_string_sequence(seq): + result = True + i = None + for i, s in enumerate(seq): + if not isinstance(s, string_types): + result = False + break + assert i is not None + return result + +PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-' + '([a-z0-9_.+-]+)', re.I) +PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)') + + +def split_filename(filename, project_name=None): + """ + Extract name, version, python version from a filename (no extension) + + Return name, version, pyver or None + """ + result = None + pyver = None + m = PYTHON_VERSION.search(filename) + if m: + pyver = m.group(1) + filename = filename[:m.start()] + if project_name and len(filename) > len(project_name) + 1: + m = re.match(re.escape(project_name) + r'\b', filename) + if m: + n = m.end() + result = filename[:n], filename[n + 1:], pyver + if result is None: + m = PROJECT_NAME_AND_VERSION.match(filename) + if m: + result = m.group(1), m.group(3), pyver + return result + +# Allow spaces in name because of legacy dists like "Twisted Core" +NAME_VERSION_RE = re.compile(r'(?P[\w .-]+)\s*' + r'\(\s*(?P[^\s)]+)\)$') + +def parse_name_and_version(p): + """ + A utility method used to get name and version from a string. + + From e.g. a Provides-Dist value. + + :param p: A value in a form 'foo (1.0)' + :return: The name and version as a tuple. + """ + m = NAME_VERSION_RE.match(p) + if not m: + raise DistlibException('Ill-formed name/version string: \'%s\'' % p) + d = m.groupdict() + return d['name'].strip().lower(), d['ver'] + +def get_extras(requested, available): + result = set() + requested = set(requested or []) + available = set(available or []) + if '*' in requested: + requested.remove('*') + result |= available + for r in requested: + if r == '-': + result.add(r) + elif r.startswith('-'): + unwanted = r[1:] + if unwanted not in available: + logger.warning('undeclared extra: %s' % unwanted) + if unwanted in result: + result.remove(unwanted) + else: + if r not in available: + logger.warning('undeclared extra: %s' % r) + result.add(r) + return result +# +# Extended metadata functionality +# + +def _get_external_data(url): + result = {} + try: + # urlopen might fail if it runs into redirections, + # because of Python issue #13696. Fixed in locators + # using a custom redirect handler. + resp = urlopen(url) + headers = resp.info() + if headers.get('Content-Type') != 'application/json': + logger.debug('Unexpected response for JSON request') + else: + reader = codecs.getreader('utf-8')(resp) + #data = reader.read().decode('utf-8') + #result = json.loads(data) + result = json.load(reader) + except Exception as e: + logger.exception('Failed to get external data for %s: %s', url, e) + return result + + +def get_project_data(name): + url = ('https://www.red-dove.com/pypi/projects/' + '%s/%s/project.json' % (name[0].upper(), name)) + result = _get_external_data(url) + return result + +def get_package_data(name, version): + url = ('https://www.red-dove.com/pypi/projects/' + '%s/%s/package-%s.json' % (name[0].upper(), name, version)) + return _get_external_data(url) + + +class Cache(object): + """ + A class implementing a cache for resources that need to live in the file system + e.g. shared libraries. This class was moved from resources to here because it + could be used by other modules, e.g. the wheel module. + """ + + def __init__(self, base): + """ + Initialise an instance. + + :param base: The base directory where the cache should be located. + """ + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if not os.path.isdir(base): + os.makedirs(base) + if (os.stat(base).st_mode & 0o77) != 0: + logger.warning('Directory \'%s\' is not private', base) + self.base = os.path.abspath(os.path.normpath(base)) + + def prefix_to_dir(self, prefix): + """ + Converts a resource prefix to a directory name in the cache. + """ + return path_to_cache_dir(prefix) + + def clear(self): + """ + Clear the cache. + """ + not_removed = [] + for fn in os.listdir(self.base): + fn = os.path.join(self.base, fn) + try: + if os.path.islink(fn) or os.path.isfile(fn): + os.remove(fn) + elif os.path.isdir(fn): + shutil.rmtree(fn) + except Exception: + not_removed.append(fn) + return not_removed + + +class EventMixin(object): + """ + A very simple publish/subscribe system. + """ + def __init__(self): + self._subscribers = {} + + def add(self, event, subscriber, append=True): + """ + Add a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be added (and called when the + event is published). + :param append: Whether to append or prepend the subscriber to an + existing subscriber list for the event. + """ + subs = self._subscribers + if event not in subs: + subs[event] = deque([subscriber]) + else: + sq = subs[event] + if append: + sq.append(subscriber) + else: + sq.appendleft(subscriber) + + def remove(self, event, subscriber): + """ + Remove a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be removed. + """ + subs = self._subscribers + if event not in subs: + raise ValueError('No subscribers: %r' % event) + subs[event].remove(subscriber) + + def get_subscribers(self, event): + """ + Return an iterator for the subscribers for an event. + :param event: The event to return subscribers for. + """ + return iter(self._subscribers.get(event, ())) + + def publish(self, event, *args, **kwargs): + """ + Publish a event and return a list of values returned by its + subscribers. + + :param event: The event to publish. + :param args: The positional arguments to pass to the event's + subscribers. + :param kwargs: The keyword arguments to pass to the event's + subscribers. + """ + result = [] + for subscriber in self.get_subscribers(event): + try: + value = subscriber(event, *args, **kwargs) + except Exception: + logger.exception('Exception during event publication') + value = None + result.append(value) + logger.debug('publish %s: args = %s, kwargs = %s, result = %s', + event, args, kwargs, result) + return result + +# +# Simple sequencing +# +class Sequencer(object): + def __init__(self): + self._preds = {} + self._succs = {} + self._nodes = set() # nodes with no preds/succs + + def add_node(self, node): + self._nodes.add(node) + + def remove_node(self, node, edges=False): + if node in self._nodes: + self._nodes.remove(node) + if edges: + for p in set(self._preds.get(node, ())): + self.remove(p, node) + for s in set(self._succs.get(node, ())): + self.remove(node, s) + # Remove empties + for k, v in list(self._preds.items()): + if not v: + del self._preds[k] + for k, v in list(self._succs.items()): + if not v: + del self._succs[k] + + def add(self, pred, succ): + assert pred != succ + self._preds.setdefault(succ, set()).add(pred) + self._succs.setdefault(pred, set()).add(succ) + + def remove(self, pred, succ): + assert pred != succ + try: + preds = self._preds[succ] + succs = self._succs[pred] + except KeyError: + raise ValueError('%r not a successor of anything' % succ) + try: + preds.remove(pred) + succs.remove(succ) + except KeyError: + raise ValueError('%r not a successor of %r' % (succ, pred)) + + def is_step(self, step): + return (step in self._preds or step in self._succs or + step in self._nodes) + + def get_steps(self, final): + if not self.is_step(final): + raise ValueError('Unknown: %r' % final) + result = [] + todo = [] + seen = set() + todo.append(final) + while todo: + step = todo.pop(0) + if step in seen: + # if a step was already seen, + # move it to the end (so it will appear earlier + # when reversed on return) ... but not for the + # final step, as that would be confusing for + # users + if step != final: + result.remove(step) + result.append(step) + else: + seen.add(step) + result.append(step) + preds = self._preds.get(step, ()) + todo.extend(preds) + return reversed(result) + + @property + def strong_connections(self): + #http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + index_counter = [0] + stack = [] + lowlinks = {} + index = {} + result = [] + + graph = self._succs + + def strongconnect(node): + # set the depth index for this node to the smallest unused index + index[node] = index_counter[0] + lowlinks[node] = index_counter[0] + index_counter[0] += 1 + stack.append(node) + + # Consider successors + try: + successors = graph[node] + except Exception: + successors = [] + for successor in successors: + if successor not in lowlinks: + # Successor has not yet been visited + strongconnect(successor) + lowlinks[node] = min(lowlinks[node],lowlinks[successor]) + elif successor in stack: + # the successor is in the stack and hence in the current + # strongly connected component (SCC) + lowlinks[node] = min(lowlinks[node],index[successor]) + + # If `node` is a root node, pop the stack and generate an SCC + if lowlinks[node] == index[node]: + connected_component = [] + + while True: + successor = stack.pop() + connected_component.append(successor) + if successor == node: break + component = tuple(connected_component) + # storing the result + result.append(component) + + for node in graph: + if node not in lowlinks: + strongconnect(node) + + return result + + @property + def dot(self): + result = ['digraph G {'] + for succ in self._preds: + preds = self._preds[succ] + for pred in preds: + result.append(' %s -> %s;' % (pred, succ)) + for node in self._nodes: + result.append(' %s;' % node) + result.append('}') + return '\n'.join(result) + +# +# Unarchiving functionality for zip, tar, tgz, tbz, whl +# + +ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', + '.tgz', '.tbz', '.whl') + +def unarchive(archive_filename, dest_dir, format=None, check=True): + + def check_path(path): + if not isinstance(path, text_type): + path = path.decode('utf-8') + p = os.path.abspath(os.path.join(dest_dir, path)) + if not p.startswith(dest_dir) or p[plen] != os.sep: + raise ValueError('path outside destination: %r' % p) + + dest_dir = os.path.abspath(dest_dir) + plen = len(dest_dir) + archive = None + if format is None: + if archive_filename.endswith(('.zip', '.whl')): + format = 'zip' + elif archive_filename.endswith(('.tar.gz', '.tgz')): + format = 'tgz' + mode = 'r:gz' + elif archive_filename.endswith(('.tar.bz2', '.tbz')): + format = 'tbz' + mode = 'r:bz2' + elif archive_filename.endswith('.tar'): + format = 'tar' + mode = 'r' + else: + raise ValueError('Unknown format for %r' % archive_filename) + try: + if format == 'zip': + archive = ZipFile(archive_filename, 'r') + if check: + names = archive.namelist() + for name in names: + check_path(name) + else: + archive = tarfile.open(archive_filename, mode) + if check: + names = archive.getnames() + for name in names: + check_path(name) + if format != 'zip' and sys.version_info[0] < 3: + # See Python issue 17153. If the dest path contains Unicode, + # tarfile extraction fails on Python 2.x if a member path name + # contains non-ASCII characters - it leads to an implicit + # bytes -> unicode conversion using ASCII to decode. + for tarinfo in archive.getmembers(): + if not isinstance(tarinfo.name, text_type): + tarinfo.name = tarinfo.name.decode('utf-8') + archive.extractall(dest_dir) + + finally: + if archive: + archive.close() + + +def zip_dir(directory): + """zip a directory tree into a BytesIO object""" + result = io.BytesIO() + dlen = len(directory) + with ZipFile(result, "w") as zf: + for root, dirs, files in os.walk(directory): + for name in files: + full = os.path.join(root, name) + rel = root[dlen:] + dest = os.path.join(rel, name) + zf.write(full, dest) + return result + +# +# Simple progress bar +# + +UNITS = ('', 'K', 'M', 'G','T','P') + + +class Progress(object): + unknown = 'UNKNOWN' + + def __init__(self, minval=0, maxval=100): + assert maxval is None or maxval >= minval + self.min = self.cur = minval + self.max = maxval + self.started = None + self.elapsed = 0 + self.done = False + + def update(self, curval): + assert self.min <= curval + assert self.max is None or curval <= self.max + self.cur = curval + now = time.time() + if self.started is None: + self.started = now + else: + self.elapsed = now - self.started + + def increment(self, incr): + assert incr >= 0 + self.update(self.cur + incr) + + def start(self): + self.update(self.min) + return self + + def stop(self): + if self.max is not None: + self.update(self.max) + self.done = True + + @property + def maximum(self): + return self.unknown if self.max is None else self.max + + @property + def percentage(self): + if self.done: + result = '100 %' + elif self.max is None: + result = ' ?? %' + else: + v = 100.0 * (self.cur - self.min) / (self.max - self.min) + result = '%3d %%' % v + return result + + def format_duration(self, duration): + if (duration <= 0) and self.max is None or self.cur == self.min: + result = '??:??:??' + #elif duration < 1: + # result = '--:--:--' + else: + result = time.strftime('%H:%M:%S', time.gmtime(duration)) + return result + + @property + def ETA(self): + if self.done: + prefix = 'Done' + t = self.elapsed + #import pdb; pdb.set_trace() + else: + prefix = 'ETA ' + if self.max is None: + t = -1 + elif self.elapsed == 0 or (self.cur == self.min): + t = 0 + else: + #import pdb; pdb.set_trace() + t = float(self.max - self.min) + t /= self.cur - self.min + t = (t - 1) * self.elapsed + return '%s: %s' % (prefix, self.format_duration(t)) + + @property + def speed(self): + if self.elapsed == 0: + result = 0.0 + else: + result = (self.cur - self.min) / self.elapsed + for unit in UNITS: + if result < 1000: + break + result /= 1000.0 + return '%d %sB/s' % (result, unit) + +# +# Glob functionality +# + +RICH_GLOB = re.compile(r'\{([^}]*)\}') +_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]') +_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') + + +def iglob(path_glob): + """Extended globbing function that supports ** and {opt1,opt2,opt3}.""" + if _CHECK_RECURSIVE_GLOB.search(path_glob): + msg = """invalid glob %r: recursive glob "**" must be used alone""" + raise ValueError(msg % path_glob) + if _CHECK_MISMATCH_SET.search(path_glob): + msg = """invalid glob %r: mismatching set marker '{' or '}'""" + raise ValueError(msg % path_glob) + return _iglob(path_glob) + + +def _iglob(path_glob): + rich_path_glob = RICH_GLOB.split(path_glob, 1) + if len(rich_path_glob) > 1: + assert len(rich_path_glob) == 3, rich_path_glob + prefix, set, suffix = rich_path_glob + for item in set.split(','): + for path in _iglob(''.join((prefix, item, suffix))): + yield path + else: + if '**' not in path_glob: + for item in std_iglob(path_glob): + yield item + else: + prefix, radical = path_glob.split('**', 1) + if prefix == '': + prefix = '.' + if radical == '': + radical = '*' + else: + # we support both + radical = radical.lstrip('/') + radical = radical.lstrip('\\') + for path, dir, files in os.walk(prefix): + path = os.path.normpath(path) + for fn in _iglob(os.path.join(path, radical)): + yield fn + + + +# +# HTTPSConnection which verifies certificates/matches domains +# + +class HTTPSConnection(httplib.HTTPSConnection): + ca_certs = None # set this to the path to the certs file (.pem) + check_domain = True # only used if ca_certs is not None + + # noinspection PyPropertyAccess + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if getattr(self, '_tunnel_host', False): + self.sock = sock + self._tunnel() + + if not hasattr(ssl, 'SSLContext'): + # For 2.x + if self.ca_certs: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + cert_reqs=cert_reqs, + ssl_version=ssl.PROTOCOL_SSLv23, + ca_certs=self.ca_certs) + else: + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.options |= ssl.OP_NO_SSLv2 + if self.cert_file: + context.load_cert_chain(self.cert_file, self.key_file) + kwargs = {} + if self.ca_certs: + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(cafile=self.ca_certs) + if getattr(ssl, 'HAS_SNI', False): + kwargs['server_hostname'] = self.host + self.sock = context.wrap_socket(sock, **kwargs) + if self.ca_certs and self.check_domain: + try: + match_hostname(self.sock.getpeercert(), self.host) + logger.debug('Host verified: %s', self.host) + except CertificateError: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + +class HTTPSHandler(BaseHTTPSHandler): + def __init__(self, ca_certs, check_domain=True): + BaseHTTPSHandler.__init__(self) + self.ca_certs = ca_certs + self.check_domain = check_domain + + def _conn_maker(self, *args, **kwargs): + """ + This is called to create a connection instance. Normally you'd + pass a connection class to do_open, but it doesn't actually check for + a class, and just expects a callable. As long as we behave just as a + constructor would have, we should be OK. If it ever changes so that + we *must* pass a class, we'll create an UnsafeHTTPSConnection class + which just sets check_domain to False in the class definition, and + choose which one to pass to do_open. + """ + result = HTTPSConnection(*args, **kwargs) + if self.ca_certs: + result.ca_certs = self.ca_certs + result.check_domain = self.check_domain + return result + + def https_open(self, req): + try: + return self.do_open(self._conn_maker, req) + except URLError as e: + if 'certificate verify failed' in str(e.reason): + raise CertificateError('Unable to verify server certificate ' + 'for %s' % req.host) + else: + raise + +# +# To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The- +# Middle proxy using HTTP listens on port 443, or an index mistakenly serves +# HTML containing a http://xyz link when it should be https://xyz), +# you can use the following handler class, which does not allow HTTP traffic. +# +# It works by inheriting from HTTPHandler - so build_opener won't add a +# handler for HTTP itself. +# +class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler): + def http_open(self, req): + raise URLError('Unexpected HTTP request on what should be a secure ' + 'connection: %s' % req) + +# +# XML-RPC with timeouts +# + +_ver_info = sys.version_info[:2] + +if _ver_info == (2, 6): + class HTTP(httplib.HTTP): + def __init__(self, host='', port=None, **kwargs): + if port == 0: # 0 means use port 0, not the default port + port = None + self._setup(self._connection_class(host, port, **kwargs)) + + + class HTTPS(httplib.HTTPS): + def __init__(self, host='', port=None, **kwargs): + if port == 0: # 0 means use port 0, not the default port + port = None + self._setup(self._connection_class(host, port, **kwargs)) + + +class Transport(xmlrpclib.Transport): + def __init__(self, timeout, use_datetime=0): + self.timeout = timeout + xmlrpclib.Transport.__init__(self, use_datetime) + + def make_connection(self, host): + h, eh, x509 = self.get_host_info(host) + if _ver_info == (2, 6): + result = HTTP(h, timeout=self.timeout) + else: + if not self._connection or host != self._connection[0]: + self._extra_headers = eh + self._connection = host, httplib.HTTPConnection(h) + result = self._connection[1] + return result + +class SafeTransport(xmlrpclib.SafeTransport): + def __init__(self, timeout, use_datetime=0): + self.timeout = timeout + xmlrpclib.SafeTransport.__init__(self, use_datetime) + + def make_connection(self, host): + h, eh, kwargs = self.get_host_info(host) + if not kwargs: + kwargs = {} + kwargs['timeout'] = self.timeout + if _ver_info == (2, 6): + result = HTTPS(host, None, **kwargs) + else: + if not self._connection or host != self._connection[0]: + self._extra_headers = eh + self._connection = host, httplib.HTTPSConnection(h, None, + **kwargs) + result = self._connection[1] + return result + + +class ServerProxy(xmlrpclib.ServerProxy): + def __init__(self, uri, **kwargs): + self.timeout = timeout = kwargs.pop('timeout', None) + # The above classes only come into play if a timeout + # is specified + if timeout is not None: + scheme, _ = splittype(uri) + use_datetime = kwargs.get('use_datetime', 0) + if scheme == 'https': + tcls = SafeTransport + else: + tcls = Transport + kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime) + self.transport = t + xmlrpclib.ServerProxy.__init__(self, uri, **kwargs) + +# +# CSV functionality. This is provided because on 2.x, the csv module can't +# handle Unicode. However, we need to deal with Unicode in e.g. RECORD files. +# + +def _csv_open(fn, mode, **kwargs): + if sys.version_info[0] < 3: + mode += 'b' + else: + kwargs['newline'] = '' + return open(fn, mode, **kwargs) + + +class CSVBase(object): + defaults = { + 'delimiter': str(','), # The strs are used because we need native + 'quotechar': str('"'), # str in the csv API (2.x won't take + 'lineterminator': str('\n') # Unicode) + } + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.stream.close() + + +class CSVReader(CSVBase): + def __init__(self, **kwargs): + if 'stream' in kwargs: + stream = kwargs['stream'] + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + self.stream = stream + else: + self.stream = _csv_open(kwargs['path'], 'r') + self.reader = csv.reader(self.stream, **self.defaults) + + def __iter__(self): + return self + + def next(self): + result = next(self.reader) + if sys.version_info[0] < 3: + for i, item in enumerate(result): + if not isinstance(item, text_type): + result[i] = item.decode('utf-8') + return result + + __next__ = next + +class CSVWriter(CSVBase): + def __init__(self, fn, **kwargs): + self.stream = _csv_open(fn, 'w') + self.writer = csv.writer(self.stream, **self.defaults) + + def writerow(self, row): + if sys.version_info[0] < 3: + r = [] + for item in row: + if isinstance(item, text_type): + item = item.encode('utf-8') + r.append(item) + row = r + self.writer.writerow(row) + +# +# Configurator functionality +# + +class Configurator(BaseConfigurator): + + value_converters = dict(BaseConfigurator.value_converters) + value_converters['inc'] = 'inc_convert' + + def __init__(self, config, base=None): + super(Configurator, self).__init__(config) + self.base = base or os.getcwd() + + def configure_custom(self, config): + def convert(o): + if isinstance(o, (list, tuple)): + result = type(o)([convert(i) for i in o]) + elif isinstance(o, dict): + if '()' in o: + result = self.configure_custom(o) + else: + result = {} + for k in o: + result[k] = convert(o[k]) + else: + result = self.convert(o) + return result + + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + args = config.pop('[]', ()) + if args: + args = tuple([convert(o) for o in args]) + items = [(k, convert(config[k])) for k in config if valid_ident(k)] + kwargs = dict(items) + result = c(*args, **kwargs) + if props: + for n, v in props.items(): + setattr(result, n, convert(v)) + return result + + def __getitem__(self, key): + result = self.config[key] + if isinstance(result, dict) and '()' in result: + self.config[key] = result = self.configure_custom(result) + return result + + def inc_convert(self, value): + """Default converter for the inc:// protocol.""" + if not os.path.isabs(value): + value = os.path.join(self.base, value) + with codecs.open(value, 'r', encoding='utf-8') as f: + result = json.load(f) + return result + +# +# Mixin for running subprocesses and capturing their output +# + +class SubprocessMixin(object): + def __init__(self, verbose=False, progress=None): + self.verbose = verbose + self.progress = progress + + def reader(self, stream, context): + """ + Read lines from a subprocess' output stream and either pass to a progress + callable (if specified) or write progress information to sys.stderr. + """ + progress = self.progress + verbose = self.verbose + while True: + s = stream.readline() + if not s: + break + if progress is not None: + progress(s, context) + else: + if not verbose: + sys.stderr.write('.') + else: + sys.stderr.write(s.decode('utf-8')) + sys.stderr.flush() + stream.close() + + def run_command(self, cmd, **kwargs): + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, **kwargs) + t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout')) + t1.start() + t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr')) + t2.start() + p.wait() + t1.join() + t2.join() + if self.progress is not None: + self.progress('done.', 'main') + elif self.verbose: + sys.stderr.write('done.\n') + return p diff --git a/src/build_utils/distlib/version.py b/src/build_utils/distlib/version.py new file mode 100644 index 0000000000..f0e62c4ee5 --- /dev/null +++ b/src/build_utils/distlib/version.py @@ -0,0 +1,721 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2013 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Implementation of a flexible versioning scheme providing support for PEP-386, +distribute-compatible and semantic versioning. +""" + +import logging +import re + +from .compat import string_types + +__all__ = ['NormalizedVersion', 'NormalizedMatcher', + 'LegacyVersion', 'LegacyMatcher', + 'SemanticVersion', 'SemanticMatcher', + 'UnsupportedVersionError', 'get_scheme'] + +logger = logging.getLogger(__name__) + + +class UnsupportedVersionError(ValueError): + """This is an unsupported version.""" + pass + + +class Version(object): + def __init__(self, s): + self._string = s = s.strip() + self._parts = parts = self.parse(s) + assert isinstance(parts, tuple) + assert len(parts) > 0 + + def parse(self, s): + raise NotImplementedError('please implement in a subclass') + + def _check_compatible(self, other): + if type(self) != type(other): + raise TypeError('cannot compare %r and %r' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + self._check_compatible(other) + return self._parts < other._parts + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__lt__(other) or self.__eq__(other) + + def __ge__(self, other): + return self.__gt__(other) or self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self._parts) + + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + @property + def is_prerelease(self): + raise NotImplementedError('Please implement in subclasses.') + + +class Matcher(object): + version_class = None + + dist_re = re.compile(r"^(\w[\s\w'.-]*)(\((.*)\))?") + comp_re = re.compile(r'^(<=|>=|<|>|!=|==|~=)?\s*([^\s,]+)$') + num_re = re.compile(r'^\d+(\.\d+)*$') + + # value is either a callable or the name of a method + _operators = { + '<': lambda v, c, p: v < c, + '>': lambda v, c, p: v > c, + '<=': lambda v, c, p: v == c or v < c, + '>=': lambda v, c, p: v == c or v > c, + '==': lambda v, c, p: v == c, + # by default, compatible => >=. + '~=': lambda v, c, p: v == c or v > c, + '!=': lambda v, c, p: v != c, + } + + def __init__(self, s): + if self.version_class is None: + raise ValueError('Please specify a version class') + self._string = s = s.strip() + m = self.dist_re.match(s) + if not m: + raise ValueError('Not valid: %r' % s) + groups = m.groups('') + self.name = groups[0].strip() + self.key = self.name.lower() # for case-insensitive comparisons + clist = [] + if groups[2]: + constraints = [c.strip() for c in groups[2].split(',')] + for c in constraints: + m = self.comp_re.match(c) + if not m: + raise ValueError('Invalid %r in %r' % (c, s)) + groups = m.groups() + op = groups[0] or '~=' + s = groups[1] + if s.endswith('.*'): + if op not in ('==', '!='): + raise ValueError('\'.*\' not allowed for ' + '%r constraints' % op) + # Could be a partial version (e.g. for '2.*') which + # won't parse as a version, so keep it as a string + vn, prefix = s[:-2], True + if not self.num_re.match(vn): + # Just to check that vn is a valid version + self.version_class(vn) + else: + # Should parse as a version, so we can create an + # instance for the comparison + vn, prefix = self.version_class(s), False + clist.append((op, vn, prefix)) + self._parts = tuple(clist) + + def match(self, version): + """ + Check if the provided version matches the constraints. + + :param version: The version to match against this instance. + :type version: Strring or :class:`Version` instance. + """ + if isinstance(version, string_types): + version = self.version_class(version) + for operator, constraint, prefix in self._parts: + f = self._operators.get(operator) + if isinstance(f, string_types): + f = getattr(self, f) + if not f: + msg = ('%r not implemented ' + 'for %s' % (operator, self.__class__.__name__)) + raise NotImplementedError(msg) + if not f(version, constraint, prefix): + return False + return True + + @property + def exact_version(self): + result = None + if len(self._parts) == 1 and self._parts[0][0] == '==': + result = self._parts[0][1] + return result + + def _check_compatible(self, other): + if type(self) != type(other) or self.name != other.name: + raise TypeError('cannot compare %s and %s' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self.key == other.key and self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self.key) + hash(self._parts) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + +PEP426_VERSION_RE = re.compile(r'^(\d+(\.\d+)*)((a|b|c|rc)(\d+))?' + r'(\.(post)(\d+))?(\.(dev)(\d+))?' + r'(-(\d+(\.\d+)?))?$') + + +def _pep426_key(s): + s = s.strip() + m = PEP426_VERSION_RE.match(s) + if not m: + raise UnsupportedVersionError('Not a valid version: %s' % s) + groups = m.groups() + nums = tuple(int(v) for v in groups[0].split('.')) + while len(nums) > 1 and nums[-1] == 0: + nums = nums[:-1] + + pre = groups[3:5] + post = groups[6:8] + dev = groups[9:11] + local = groups[12] + if pre == (None, None): + pre = () + else: + pre = pre[0], int(pre[1]) + if post == (None, None): + post = () + else: + post = post[0], int(post[1]) + if dev == (None, None): + dev = () + else: + dev = dev[0], int(dev[1]) + if local is None: + local = () + else: + local = tuple([int(s) for s in local.split('.')]) + if not pre: + # either before pre-release, or final release and after + if not post and dev: + # before pre-release + pre = ('a', -1) # to sort before a0 + else: + pre = ('z',) # to sort after all pre-releases + # now look at the state of post and dev. + if not post: + post = ('_',) # sort before 'a' + if not dev: + dev = ('final',) + + #print('%s -> %s' % (s, m.groups())) + return nums, pre, post, dev, local + + +_normalized_key = _pep426_key + + +class NormalizedVersion(Version): + """A rational version. + + Good: + 1.2 # equivalent to "1.2.0" + 1.2.0 + 1.2a1 + 1.2.3a2 + 1.2.3b1 + 1.2.3c1 + 1.2.3.4 + TODO: fill this out + + Bad: + 1 # mininum two numbers + 1.2a # release level must have a release serial + 1.2.3b + """ + def parse(self, s): + result = _normalized_key(s) + # _normalized_key loses trailing zeroes in the release + # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0 + # However, PEP 440 prefix matching needs it: for example, + # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0). + m = PEP426_VERSION_RE.match(s) # must succeed + groups = m.groups() + self._release_clause = tuple(int(v) for v in groups[0].split('.')) + return result + + PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev']) + + @property + def is_prerelease(self): + return any(t[0] in self.PREREL_TAGS for t in self._parts if t) + + +def _match_prefix(x, y): + x = str(x) + y = str(y) + if x == y: + return True + if not x.startswith(y): + return False + n = len(y) + return x[n] == '.' + + +class NormalizedMatcher(Matcher): + version_class = NormalizedVersion + + # value is either a callable or the name of a method + _operators = { + '~=': '_match_compatible', + '<': '_match_lt', + '>': '_match_gt', + '<=': '_match_le', + '>=': '_match_ge', + '==': '_match_eq', + '!=': '_match_ne', + } + + def _adjust_local(self, version, constraint, prefix): + if prefix: + strip_local = '-' not in constraint and version._parts[-1] + else: + # both constraint and version are + # NormalizedVersion instances. + # If constraint does not have a local component, + # ensure the version doesn't, either. + strip_local = not constraint._parts[-1] and version._parts[-1] + if strip_local: + s = version._string.split('-', 1)[0] + version = self.version_class(s) + return version, constraint + + def _match_lt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version >= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_gt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version <= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_le(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version <= constraint + + def _match_ge(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version >= constraint + + def _match_eq(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version == constraint) + else: + result = _match_prefix(version, constraint) + return result + + def _match_ne(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version != constraint) + else: + result = not _match_prefix(version, constraint) + return result + + def _match_compatible(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version == constraint: + return True + if version < constraint: + return False + release_clause = constraint._release_clause + if len(release_clause) > 1: + release_clause = release_clause[:-1] + pfx = '.'.join([str(i) for i in release_clause]) + return _match_prefix(version, pfx) + +_REPLACEMENTS = ( + (re.compile('[.+-]$'), ''), # remove trailing puncts + (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start + (re.compile('^[.-]'), ''), # remove leading puncts + (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses + (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha + (re.compile(r'\b(pre-alpha|prealpha)\b'), + 'pre.alpha'), # standardise + (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses +) + +_SUFFIX_REPLACEMENTS = ( + (re.compile('^[:~._+-]+'), ''), # remove leading puncts + (re.compile('[,*")([\]]'), ''), # remove unwanted chars + (re.compile('[~:+_ -]'), '.'), # replace illegal chars + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\.$'), ''), # trailing '.' +) + +_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)') + + +def _suggest_semantic_version(s): + """ + Try to suggest a semantic form for a version for which + _suggest_normalized_version couldn't come up with anything. + """ + result = s.strip().lower() + for pat, repl in _REPLACEMENTS: + result = pat.sub(repl, result) + if not result: + result = '0.0.0' + + # Now look for numeric prefix, and separate it out from + # the rest. + #import pdb; pdb.set_trace() + m = _NUMERIC_PREFIX.match(result) + if not m: + prefix = '0.0.0' + suffix = result + else: + prefix = m.groups()[0].split('.') + prefix = [int(i) for i in prefix] + while len(prefix) < 3: + prefix.append(0) + if len(prefix) == 3: + suffix = result[m.end():] + else: + suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():] + prefix = prefix[:3] + prefix = '.'.join([str(i) for i in prefix]) + suffix = suffix.strip() + if suffix: + #import pdb; pdb.set_trace() + # massage the suffix. + for pat, repl in _SUFFIX_REPLACEMENTS: + suffix = pat.sub(repl, suffix) + + if not suffix: + result = prefix + else: + sep = '-' if 'dev' in suffix else '+' + result = prefix + sep + suffix + if not is_semver(result): + result = None + return result + + +def _suggest_normalized_version(s): + """Suggest a normalized version close to the given version string. + + If you have a version string that isn't rational (i.e. NormalizedVersion + doesn't like it) then you might be able to get an equivalent (or close) + rational version from this function. + + This does a number of simple normalizations to the given string, based + on observation of versions currently in use on PyPI. Given a dump of + those version during PyCon 2009, 4287 of them: + - 2312 (53.93%) match NormalizedVersion without change + with the automatic suggestion + - 3474 (81.04%) match when using this suggestion method + + @param s {str} An irrational version string. + @returns A rational version string, or None, if couldn't determine one. + """ + try: + _normalized_key(s) + return s # already rational + except UnsupportedVersionError: + pass + + rs = s.lower() + + # part of this could use maketrans + for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), + ('beta', 'b'), ('rc', 'c'), ('-final', ''), + ('-pre', 'c'), + ('-release', ''), ('.release', ''), ('-stable', ''), + ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), + ('final', '')): + rs = rs.replace(orig, repl) + + # if something ends with dev or pre, we add a 0 + rs = re.sub(r"pre$", r"pre0", rs) + rs = re.sub(r"dev$", r"dev0", rs) + + # if we have something like "b-2" or "a.2" at the end of the + # version, that is pobably beta, alpha, etc + # let's remove the dash or dot + rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs) + + # 1.0-dev-r371 -> 1.0.dev371 + # 0.1-dev-r79 -> 0.1.dev79 + rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) + + # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 + rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) + + # Clean: v0.3, v1.0 + if rs.startswith('v'): + rs = rs[1:] + + # Clean leading '0's on numbers. + #TODO: unintended side-effect on, e.g., "2003.05.09" + # PyPI stats: 77 (~2%) better + rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) + + # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers + # zero. + # PyPI stats: 245 (7.56%) better + rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) + + # the 'dev-rNNN' tag is a dev tag + rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) + + # clean the - when used as a pre delimiter + rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) + + # a terminal "dev" or "devel" can be changed into ".dev0" + rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) + + # a terminal "dev" can be changed into ".dev0" + rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) + + # a terminal "final" or "stable" can be removed + rs = re.sub(r"(final|stable)$", "", rs) + + # The 'r' and the '-' tags are post release tags + # 0.4a1.r10 -> 0.4a1.post10 + # 0.9.33-17222 -> 0.9.33.post17222 + # 0.9.33-r17222 -> 0.9.33.post17222 + rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) + + # Clean 'r' instead of 'dev' usage: + # 0.9.33+r17222 -> 0.9.33.dev17222 + # 1.0dev123 -> 1.0.dev123 + # 1.0.git123 -> 1.0.dev123 + # 1.0.bzr123 -> 1.0.dev123 + # 0.1a0dev.123 -> 0.1a0.dev123 + # PyPI stats: ~150 (~4%) better + rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) + + # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: + # 0.2.pre1 -> 0.2c1 + # 0.2-c1 -> 0.2c1 + # 1.0preview123 -> 1.0c123 + # PyPI stats: ~21 (0.62%) better + rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) + + # Tcl/Tk uses "px" for their post release markers + rs = re.sub(r"p(\d+)$", r".post\1", rs) + + try: + _normalized_key(rs) + except UnsupportedVersionError: + rs = None + return rs + +# +# Legacy version processing (distribute-compatible) +# + +_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I) +_VERSION_REPLACE = { + 'pre': 'c', + 'preview': 'c', + '-': 'final-', + 'rc': 'c', + 'dev': '@', + '': None, + '.': None, +} + + +def _legacy_key(s): + def get_parts(s): + result = [] + for p in _VERSION_PART.split(s.lower()): + p = _VERSION_REPLACE.get(p, p) + if p: + if '0' <= p[:1] <= '9': + p = p.zfill(8) + else: + p = '*' + p + result.append(p) + result.append('*final') + return result + + result = [] + for p in get_parts(s): + if p.startswith('*'): + if p < '*final': + while result and result[-1] == '*final-': + result.pop() + while result and result[-1] == '00000000': + result.pop() + result.append(p) + return tuple(result) + + +class LegacyVersion(Version): + def parse(self, s): + return _legacy_key(s) + + @property + def is_prerelease(self): + result = False + for x in self._parts: + if (isinstance(x, string_types) and x.startswith('*') and + x < '*final'): + result = True + break + return result + + +class LegacyMatcher(Matcher): + version_class = LegacyVersion + + _operators = dict(Matcher._operators) + _operators['~='] = '_match_compatible' + + numeric_re = re.compile('^(\d+(\.\d+)*)') + + def _match_compatible(self, version, constraint, prefix): + if version < constraint: + return False + m = self.numeric_re.match(str(constraint)) + if not m: + logger.warning('Cannot compute compatible match for version %s ' + ' and constraint %s', version, constraint) + return True + s = m.groups()[0] + if '.' in s: + s = s.rsplit('.', 1)[0] + return _match_prefix(version, s) + +# +# Semantic versioning +# + +_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)' + r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?' + r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I) + + +def is_semver(s): + return _SEMVER_RE.match(s) + + +def _semantic_key(s): + def make_tuple(s, absent): + if s is None: + result = (absent,) + else: + parts = s[1:].split('.') + # We can't compare ints and strings on Python 3, so fudge it + # by zero-filling numeric values so simulate a numeric comparison + result = tuple([p.zfill(8) if p.isdigit() else p for p in parts]) + return result + + m = is_semver(s) + if not m: + raise UnsupportedVersionError(s) + groups = m.groups() + major, minor, patch = [int(i) for i in groups[:3]] + # choose the '|' and '*' so that versions sort correctly + pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*') + return (major, minor, patch), pre, build + + +class SemanticVersion(Version): + def parse(self, s): + return _semantic_key(s) + + @property + def is_prerelease(self): + return self._parts[1][0] != '|' + + +class SemanticMatcher(Matcher): + version_class = SemanticVersion + + +class VersionScheme(object): + def __init__(self, key, matcher, suggester=None): + self.key = key + self.matcher = matcher + self.suggester = suggester + + def is_valid_version(self, s): + try: + self.matcher.version_class(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_matcher(self, s): + try: + self.matcher(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_constraint_list(self, s): + """ + Used for processing some metadata fields + """ + return self.is_valid_matcher('dummy_name (%s)' % s) + + def suggest(self, s): + if self.suggester is None: + result = None + else: + result = self.suggester(s) + return result + +_SCHEMES = { + 'normalized': VersionScheme(_normalized_key, NormalizedMatcher, + _suggest_normalized_version), + 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s), + 'semantic': VersionScheme(_semantic_key, SemanticMatcher, + _suggest_semantic_version), +} + +_SCHEMES['default'] = _SCHEMES['normalized'] + + +def get_scheme(name): + if name not in _SCHEMES: + raise ValueError('unknown scheme name: %r' % name) + return _SCHEMES[name] diff --git a/src/build_utils/distlib/w32.exe b/src/build_utils/distlib/w32.exe new file mode 100644 index 0000000000000000000000000000000000000000..b6e1a0a238c4a70d002caad6c80c88d49159a759 GIT binary patch literal 88576 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

8HnXK; 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 A[^-]+) +-(?P\d+[^-]*) +(-(?P\d+[^-]*))? +-(?P\w+\d+(\.\w+\d+)*) +-(?P\w+) +-(?P\w+) +\.whl$ +''', re.IGNORECASE | re.VERBOSE) + +NAME_VERSION_RE = re.compile(r''' +(?P[^-]+) +-(?P\d+[^-]*) +(-(?P\d+[^-]*))?$ +''', re.IGNORECASE | re.VERBOSE) + +SHEBANG_RE = re.compile(br'\s*#![^\r\n]*') +SHEBANG_DETAIL_RE = re.compile(br'^(\s*#!("[^"]+"|\S+))\s+(.*)$') +SHEBANG_PYTHON = b'#!python' +SHEBANG_PYTHONW = b'#!pythonw' + +if os.sep == '/': + to_posix = lambda o: o +else: + to_posix = lambda o: o.replace(os.sep, '/') + + +class Mounter(object): + def __init__(self): + self.impure_wheels = {} + self.libs = {} + + def add(self, pathname, extensions): + self.impure_wheels[pathname] = extensions + self.libs.update(extensions) + + def remove(self, pathname): + extensions = self.impure_wheels.pop(pathname) + for k, v in extensions: + if k in self.libs: + del self.libs[k] + + def find_module(self, fullname, path=None): + if fullname in self.libs: + result = self + else: + result = None + return result + + def load_module(self, fullname): + if fullname in sys.modules: + result = sys.modules[fullname] + else: + if fullname not in self.libs: + raise ImportError('unable to find extension for %s' % fullname) + result = imp.load_dynamic(fullname, self.libs[fullname]) + result.__loader__ = self + parts = fullname.rsplit('.', 1) + if len(parts) > 1: + result.__package__ = parts[0] + return result + +_hook = Mounter() + + +class Wheel(object): + """ + Class to build and install from Wheel files (PEP 427). + """ + + wheel_version = (1, 1) + hash_kind = 'sha256' + + def __init__(self, filename=None, sign=False, verify=False): + """ + Initialise an instance using a (valid) filename. + """ + self.sign = sign + self.should_verify = verify + self.buildver = '' + self.pyver = [PYVER] + self.abi = ['none'] + self.arch = ['any'] + self.dirname = os.getcwd() + if filename is None: + self.name = 'dummy' + self.version = '0.1' + self._filename = self.filename + else: + m = NAME_VERSION_RE.match(filename) + if m: + info = m.groupdict('') + self.name = info['nm'] + # Reinstate the local version separator + self.version = info['vn'].replace('_', '-') + self.buildver = info['bn'] + self._filename = self.filename + else: + dirname, filename = os.path.split(filename) + m = FILENAME_RE.match(filename) + if not m: + raise DistlibException('Invalid name or ' + 'filename: %r' % filename) + if dirname: + self.dirname = os.path.abspath(dirname) + self._filename = filename + info = m.groupdict('') + self.name = info['nm'] + self.version = info['vn'] + self.buildver = info['bn'] + self.pyver = info['py'].split('.') + self.abi = info['bi'].split('.') + self.arch = info['ar'].split('.') + + @property + def filename(self): + """ + Build and return a filename from the various components. + """ + if self.buildver: + buildver = '-' + self.buildver + else: + buildver = '' + pyver = '.'.join(self.pyver) + abi = '.'.join(self.abi) + arch = '.'.join(self.arch) + # replace - with _ as a local version separator + version = self.version.replace('-', '_') + return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, + pyver, abi, arch) + + @property + def exists(self): + path = os.path.join(self.dirname, self.filename) + return os.path.isfile(path) + + @property + def tags(self): + for pyver in self.pyver: + for abi in self.abi: + for arch in self.arch: + yield pyver, abi, arch + + @cached_property + def metadata(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + wrapper = codecs.getreader('utf-8') + with ZipFile(pathname, 'r') as zf: + wheel_metadata = self.get_wheel_metadata(zf) + wv = wheel_metadata['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + if file_version < (1, 1): + fn = 'METADATA' + else: + fn = METADATA_FILENAME + try: + metadata_filename = posixpath.join(info_dir, fn) + with zf.open(metadata_filename) as bf: + wf = wrapper(bf) + result = Metadata(fileobj=wf) + except KeyError: + raise ValueError('Invalid wheel, because %s is ' + 'missing' % fn) + return result + + def get_wheel_metadata(self, zf): + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + metadata_filename = posixpath.join(info_dir, 'WHEEL') + with zf.open(metadata_filename) as bf: + wf = codecs.getreader('utf-8')(bf) + message = message_from_file(wf) + return dict(message) + + @cached_property + def info(self): + pathname = os.path.join(self.dirname, self.filename) + with ZipFile(pathname, 'r') as zf: + result = self.get_wheel_metadata(zf) + return result + + def process_shebang(self, data): + m = SHEBANG_RE.match(data) + if m: + end = m.end() + shebang, data_after_shebang = data[:end], data[end:] + # Preserve any arguments after the interpreter + if b'pythonw' in shebang.lower(): + shebang_python = SHEBANG_PYTHONW + else: + shebang_python = SHEBANG_PYTHON + m = SHEBANG_DETAIL_RE.match(shebang) + if m: + args = b' ' + m.groups()[-1] + else: + args = b'' + shebang = shebang_python + args + data = shebang + data_after_shebang + else: + cr = data.find(b'\r') + lf = data.find(b'\n') + if cr < 0 or cr > lf: + term = b'\n' + else: + if data[cr:cr + 2] == b'\r\n': + term = b'\r\n' + else: + term = b'\r' + data = SHEBANG_PYTHON + term + data + return data + + def get_hash(self, data, hash_kind=None): + if hash_kind is None: + hash_kind = self.hash_kind + try: + hasher = getattr(hashlib, hash_kind) + except AttributeError: + raise DistlibException('Unsupported hash algorithm: %r' % hash_kind) + result = hasher(data).digest() + result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii') + return hash_kind, result + + def write_record(self, records, record_path, base): + with CSVWriter(record_path) as writer: + for row in records: + writer.writerow(row) + p = to_posix(os.path.relpath(record_path, base)) + writer.writerow((p, '', '')) + + def write_records(self, info, libdir, archive_paths): + records = [] + distinfo, info_dir = info + hasher = getattr(hashlib, self.hash_kind) + for ap, p in archive_paths: + with open(p, 'rb') as f: + data = f.read() + digest = '%s=%s' % self.get_hash(data) + size = os.path.getsize(p) + records.append((ap, digest, size)) + + p = os.path.join(distinfo, 'RECORD') + self.write_record(records, p, libdir) + ap = to_posix(os.path.join(info_dir, 'RECORD')) + archive_paths.append((ap, p)) + + def build_zip(self, pathname, archive_paths): + with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf: + for ap, p in archive_paths: + logger.debug('Wrote %s to %s in wheel', p, ap) + zf.write(p, ap) + + def build(self, paths, tags=None, wheel_version=None): + """ + Build a wheel from files in specified paths, and use any specified tags + when determining the name of the wheel. + """ + if tags is None: + tags = {} + + libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0] + if libkey == 'platlib': + is_pure = 'false' + default_pyver = [IMPVER] + default_abi = [ABI] + default_arch = [ARCH] + else: + is_pure = 'true' + default_pyver = [PYVER] + default_abi = ['none'] + default_arch = ['any'] + + self.pyver = tags.get('pyver', default_pyver) + self.abi = tags.get('abi', default_abi) + self.arch = tags.get('arch', default_arch) + + libdir = paths[libkey] + + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + archive_paths = [] + + # First, stuff which is not in site-packages + for key in ('data', 'headers', 'scripts'): + if key not in paths: + continue + path = paths[key] + if os.path.isdir(path): + for root, dirs, files in os.walk(path): + for fn in files: + p = fsdecode(os.path.join(root, fn)) + rp = os.path.relpath(p, path) + ap = to_posix(os.path.join(data_dir, key, rp)) + archive_paths.append((ap, p)) + if key == 'scripts' and not p.endswith('.exe'): + with open(p, 'rb') as f: + data = f.read() + data = self.process_shebang(data) + with open(p, 'wb') as f: + f.write(data) + + # Now, stuff which is in site-packages, other than the + # distinfo stuff. + path = libdir + distinfo = None + for root, dirs, files in os.walk(path): + if root == path: + # At the top level only, save distinfo for later + # and skip it for now + for i, dn in enumerate(dirs): + dn = fsdecode(dn) + if dn.endswith('.dist-info'): + distinfo = os.path.join(root, dn) + del dirs[i] + break + assert distinfo, '.dist-info directory expected, not found' + + for fn in files: + # comment out next suite to leave .pyc files in + if fsdecode(fn).endswith(('.pyc', '.pyo')): + continue + p = os.path.join(root, fn) + rp = to_posix(os.path.relpath(p, path)) + archive_paths.append((rp, p)) + + # Now distinfo. Assumed to be flat, i.e. os.listdir is enough. + files = os.listdir(distinfo) + for fn in files: + if fn not in ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'): + p = fsdecode(os.path.join(distinfo, fn)) + ap = to_posix(os.path.join(info_dir, fn)) + archive_paths.append((ap, p)) + + wheel_metadata = [ + 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version), + 'Generator: distlib %s' % __version__, + 'Root-Is-Purelib: %s' % is_pure, + ] + for pyver, abi, arch in self.tags: + wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch)) + p = os.path.join(distinfo, 'WHEEL') + with open(p, 'w') as f: + f.write('\n'.join(wheel_metadata)) + ap = to_posix(os.path.join(info_dir, 'WHEEL')) + archive_paths.append((ap, p)) + + # Now, at last, RECORD. + # Paths in here are archive paths - nothing else makes sense. + self.write_records((distinfo, info_dir), libdir, archive_paths) + # Now, ready to build the zip file + pathname = os.path.join(self.dirname, self.filename) + self.build_zip(pathname, archive_paths) + return pathname + + def install(self, paths, maker, **kwargs): + """ + Install a wheel to the specified paths. If kwarg ``warner`` is + specified, it should be a callable, which will be called with two + tuples indicating the wheel version of this software and the wheel + version in the file, if there is a discrepancy in the versions. + This can be used to issue any warnings to raise any exceptions. + If kwarg ``lib_only`` is True, only the purelib/platlib files are + installed, and the headers, scripts, data and dist-info metadata are + not written. + + The return value is a :class:`InstalledDistribution` instance unless + ``options.lib_only`` is True, in which case the return value is ``None``. + """ + + dry_run = maker.dry_run + warner = kwargs.get('warner') + lib_only = kwargs.get('lib_only', False) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message = message_from_file(wf) + wv = message['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + if (file_version != self.wheel_version) and warner: + warner(self.wheel_version, file_version) + + if message['Root-Is-Purelib'] == 'true': + libdir = paths['purelib'] + else: + libdir = paths['platlib'] + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + data_pfx = posixpath.join(data_dir, '') + info_pfx = posixpath.join(info_dir, '') + script_pfx = posixpath.join(data_dir, 'scripts', '') + + # make a new instance rather than a copy of maker's, + # as we mutate it + fileop = FileOperator(dry_run=dry_run) + fileop.record = True # so we can rollback if needed + + bc = not sys.dont_write_bytecode # Double negatives. Lovely! + + outfiles = [] # for RECORD writing + + # for script copying/shebang processing + workdir = tempfile.mkdtemp() + # set target dir later + # we default add_launchers to False, as the + # Python Launcher should be used instead + maker.source_dir = workdir + maker.target_dir = None + try: + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + if u_arcname.endswith('/RECORD.jws'): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + if lib_only and u_arcname.startswith((info_pfx, data_pfx)): + logger.debug('lib_only: skipping %s', u_arcname) + continue + is_script = (u_arcname.startswith(script_pfx) + and not u_arcname.endswith('.exe')) + + if u_arcname.startswith(data_pfx): + _, where, rp = u_arcname.split('/', 2) + outfile = os.path.join(paths[where], convert_path(rp)) + else: + # meant for site-packages. + if u_arcname in (wheel_metadata_name, record_name): + continue + outfile = os.path.join(libdir, convert_path(u_arcname)) + if not is_script: + with zf.open(arcname) as bf: + fileop.copy_stream(bf, outfile) + outfiles.append(outfile) + # Double check the digest of the written file + if not dry_run and row[1]: + with open(outfile, 'rb') as bf: + data = bf.read() + _, newdigest = self.get_hash(data, kind) + if newdigest != digest: + raise DistlibException('digest mismatch ' + 'on write for ' + '%s' % outfile) + if bc and outfile.endswith('.py'): + try: + pyc = fileop.byte_compile(outfile) + outfiles.append(pyc) + except Exception: + # Don't give up if byte-compilation fails, + # but log it and perhaps warn the user + logger.warning('Byte-compilation failed', + exc_info=True) + else: + fn = os.path.basename(convert_path(arcname)) + workname = os.path.join(workdir, fn) + with zf.open(arcname) as bf: + fileop.copy_stream(bf, workname) + + dn, fn = os.path.split(outfile) + maker.target_dir = dn + filenames = maker.make(fn) + fileop.set_executable_mode(filenames) + outfiles.extend(filenames) + + if lib_only: + logger.debug('lib_only: returning None') + dist = None + else: + # Generate scripts + + # Try to get pydist.json so we can see if there are + # any commands to generate. If this fails (e.g. because + # of a legacy wheel), log a warning but don't give up. + commands = None + file_version = self.info['Wheel-Version'] + if file_version == '1.0': + # Use legacy info + ep = posixpath.join(info_dir, 'entry_points.txt') + try: + with zf.open(ep) as bwf: + epdata = read_exports(bwf) + commands = {} + for key in ('console', 'gui'): + k = '%s_scripts' % key + if k in epdata: + commands['wrap_%s' % key] = d = {} + for v in epdata[k].values(): + s = '%s:%s' % (v.prefix, v.suffix) + if v.flags: + s += ' %s' % v.flags + d[v.name] = s + except Exception: + logger.warning('Unable to read legacy script ' + 'metadata, so cannot generate ' + 'scripts') + else: + try: + with zf.open(metadata_name) as bwf: + wf = wrapper(bwf) + commands = json.load(wf).get('extensions') + if commands: + commands = commands.get('python.commands') + except Exception: + logger.warning('Unable to read JSON metadata, so ' + 'cannot generate scripts') + if commands: + console_scripts = commands.get('wrap_console', {}) + gui_scripts = commands.get('wrap_gui', {}) + if console_scripts or gui_scripts: + script_dir = paths.get('scripts', '') + if not os.path.isdir(script_dir): + raise ValueError('Valid script path not ' + 'specified') + maker.target_dir = script_dir + for k, v in console_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script) + fileop.set_executable_mode(filenames) + + if gui_scripts: + options = {'gui': True } + for k, v in gui_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script, options) + fileop.set_executable_mode(filenames) + + p = os.path.join(libdir, info_dir) + dist = InstalledDistribution(p) + + # Write SHARED + paths = dict(paths) # don't change passed in dict + del paths['purelib'] + del paths['platlib'] + paths['lib'] = libdir + p = dist.write_shared_locations(paths, dry_run) + if p: + outfiles.append(p) + + # Write RECORD + dist.write_installed_files(outfiles, paths['prefix'], + dry_run) + return dist + except Exception: # pragma: no cover + logger.exception('installation failed.') + fileop.rollback() + raise + finally: + shutil.rmtree(workdir) + + def _get_dylib_cache(self): + global cache + if cache is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('dylib-cache'), + sys.version[:3]) + cache = Cache(base) + return cache + + def _get_extensions(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + arcname = posixpath.join(info_dir, 'EXTENSIONS') + wrapper = codecs.getreader('utf-8') + result = [] + with ZipFile(pathname, 'r') as zf: + try: + with zf.open(arcname) as bf: + wf = wrapper(bf) + extensions = json.load(wf) + cache = self._get_dylib_cache() + prefix = cache.prefix_to_dir(pathname) + cache_base = os.path.join(cache.base, prefix) + if not os.path.isdir(cache_base): + os.makedirs(cache_base) + for name, relpath in extensions.items(): + dest = os.path.join(cache_base, convert_path(relpath)) + if not os.path.exists(dest): + extract = True + else: + file_time = os.stat(dest).st_mtime + file_time = datetime.datetime.fromtimestamp(file_time) + info = zf.getinfo(relpath) + wheel_time = datetime.datetime(*info.date_time) + extract = wheel_time > file_time + if extract: + zf.extract(relpath, cache_base) + result.append((name, dest)) + except KeyError: + pass + return result + + def is_compatible(self): + """ + Determine if a wheel is compatible with the running system. + """ + return is_compatible(self) + + def is_mountable(self): + """ + Determine if a wheel is asserted as mountable by its metadata. + """ + return True # for now - metadata details TBD + + def mount(self, append=False): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if not self.is_compatible(): + msg = 'Wheel %s not compatible with this Python.' % pathname + raise DistlibException(msg) + if not self.is_mountable(): + msg = 'Wheel %s is marked as not mountable.' % pathname + raise DistlibException(msg) + if pathname in sys.path: + logger.debug('%s already in path', pathname) + else: + if append: + sys.path.append(pathname) + else: + sys.path.insert(0, pathname) + extensions = self._get_extensions() + if extensions: + if _hook not in sys.meta_path: + sys.meta_path.append(_hook) + _hook.add(pathname, extensions) + + def unmount(self): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if pathname not in sys.path: + logger.debug('%s not in path', pathname) + else: + sys.path.remove(pathname) + if pathname in _hook.impure_wheels: + _hook.remove(pathname) + if not _hook.impure_wheels: + if _hook in sys.meta_path: + sys.meta_path.remove(_hook) + + def verify(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message = message_from_file(wf) + wv = message['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + # TODO version verification + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if '..' in u_arcname: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + if u_arcname.endswith('/RECORD.jws'): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + def update(self, modifier, dest_dir=None, **kwargs): + """ + Update the contents of a wheel in a generic way. The modifier should + be a callable which expects a dictionary argument: its keys are + archive-entry paths, and its values are absolute filesystem paths + where the contents the corresponding archive entries can be found. The + modifier is free to change the contents of the files pointed to, add + new entries and remove entries, before returning. This method will + extract the entire contents of the wheel to a temporary location, call + the modifier, and then use the passed (and possibly updated) + dictionary to write a new wheel. If ``dest_dir`` is specified, the new + wheel is written there -- otherwise, the original wheel is overwritten. + + The modifier should return True if it updated the wheel, else False. + This method returns the same value the modifier returns. + """ + + def get_version(path_map, info_dir): + version = path = None + key = '%s/%s' % (info_dir, METADATA_FILENAME) + if key not in path_map: + key = '%s/PKG-INFO' % info_dir + if key in path_map: + path = path_map[key] + version = Metadata(path=path).version + return version, path + + def update_version(version, path): + updated = None + try: + v = NormalizedVersion(version) + i = version.find('-') + if i < 0: + updated = '%s-1' % version + else: + parts = [int(s) for s in version[i + 1:].split('.')] + parts[-1] += 1 + updated = '%s-%s' % (version[:i], + '.'.join(str(i) for i in parts)) + except UnsupportedVersionError: + logger.debug('Cannot update non-compliant (PEP-440) ' + 'version %r', version) + if updated: + md = Metadata(path=path) + md.version = updated + legacy = not path.endswith(METADATA_FILENAME) + md.write(path=path, legacy=legacy) + logger.debug('Version updated from %r to %r', version, + updated) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + record_name = posixpath.join(info_dir, 'RECORD') + with tempdir() as workdir: + with ZipFile(pathname, 'r') as zf: + path_map = {} + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if u_arcname == record_name: + continue + if '..' in u_arcname: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + zf.extract(zinfo, workdir) + path = os.path.join(workdir, convert_path(u_arcname)) + path_map[u_arcname] = path + + # Remember the version. + original_version, _ = get_version(path_map, info_dir) + # Files extracted. Call the modifier. + modified = modifier(path_map, **kwargs) + if modified: + # Something changed - need to build a new wheel. + current_version, path = get_version(path_map, info_dir) + if current_version and (current_version == original_version): + # Add or update local version to signify changes. + update_version(current_version, path) + # Decide where the new wheel goes. + if dest_dir is None: + fd, newpath = tempfile.mkstemp(suffix='.whl', + prefix='wheel-update-', + dir=workdir) + os.close(fd) + else: + if not os.path.isdir(dest_dir): + raise DistlibException('Not a directory: %r' % dest_dir) + newpath = os.path.join(dest_dir, self.filename) + archive_paths = list(path_map.items()) + distinfo = os.path.join(workdir, info_dir) + info = distinfo, info_dir + self.write_records(info, workdir, archive_paths) + self.build_zip(newpath, archive_paths) + if dest_dir is None: + shutil.copyfile(newpath, pathname) + return modified + +def compatible_tags(): + """ + Return (pyver, abi, arch) tuples compatible with this Python. + """ + versions = [VER_SUFFIX] + major = VER_SUFFIX[0] + for minor in range(sys.version_info[1] - 1, - 1, -1): + versions.append(''.join([major, str(minor)])) + + abis = [] + for suffix, _, _ in imp.get_suffixes(): + if suffix.startswith('.abi'): + abis.append(suffix.split('.', 2)[1]) + abis.sort() + if ABI != 'none': + abis.insert(0, ABI) + abis.append('none') + result = [] + + arches = [ARCH] + if sys.platform == 'darwin': + m = re.match('(\w+)_(\d+)_(\d+)_(\w+)$', ARCH) + if m: + name, major, minor, arch = m.groups() + minor = int(minor) + matches = [arch] + if arch in ('i386', 'ppc'): + matches.append('fat') + if arch in ('i386', 'ppc', 'x86_64'): + matches.append('fat3') + if arch in ('ppc64', 'x86_64'): + matches.append('fat64') + if arch in ('i386', 'x86_64'): + matches.append('intel') + if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'): + matches.append('universal') + while minor >= 0: + for match in matches: + s = '%s_%s_%s_%s' % (name, major, minor, match) + if s != ARCH: # already there + arches.append(s) + minor -= 1 + + # Most specific - our Python version, ABI and arch + for abi in abis: + for arch in arches: + result.append((''.join((IMP_PREFIX, versions[0])), abi, arch)) + + # where no ABI / arch dependency, but IMP_PREFIX dependency + for i, version in enumerate(versions): + result.append((''.join((IMP_PREFIX, version)), 'none', 'any')) + if i == 0: + result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any')) + + # no IMP_PREFIX, ABI or arch dependency + for i, version in enumerate(versions): + result.append((''.join(('py', version)), 'none', 'any')) + if i == 0: + result.append((''.join(('py', version[0])), 'none', 'any')) + return set(result) + + +COMPATIBLE_TAGS = compatible_tags() + +del compatible_tags + + +def is_compatible(wheel, tags=None): + if not isinstance(wheel, Wheel): + wheel = Wheel(wheel) # assume it's a filename + result = False + if tags is None: + tags = COMPATIBLE_TAGS + for ver, abi, arch in tags: + if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch: + result = True + break + return result diff --git a/src/build_utils/make_README.py b/src/build_utils/make_README.py new file mode 100755 index 0000000000..6ba456473e --- /dev/null +++ b/src/build_utils/make_README.py @@ -0,0 +1,85 @@ +""" +Generates the RestructuredText 'README' file from the markdown 'README.md' file. +Be careful: it isn't foolproof, use of fancier markdown features will probably +break it. The README file is used by Pypi to create the Rez frontpage. +""" +from __future__ import with_statement +import os.path + + +if __name__ == "__main__": + build_utils_path = os.path.dirname(__file__) + src_path = os.path.dirname(build_utils_path) + source_path = os.path.dirname(src_path) + + readme_md = os.path.join(source_path, "README.md") + with open(readme_md) as f: + content = f.read().strip() + + lines = content.split('\n') + currln = None + dest = [] + + def _flushln(): + global currln + if currln is not None: + dest.append(currln) + currln = None + + # remove line wraps + for ln in lines: + if (ln == '') or ln.startswith(' ') or ln.startswith('#') \ + or ln.startswith('* '): + _flushln() + + if (ln == '') or ln.startswith(' ') or ln.startswith('#'): + dest.append(ln) + else: + if currln is None: + currln = ln + else: + currln += ' ' + ln.strip() + + _flushln() + lines = dest + dest = [] + prev = None + curr = None + + # reformat lines + for ln in lines: + prev = curr + curr = None + toks = ln.split() + + if (ln == ''): + if prev == 'code': + dest.append(' ') + curr = 'code' + else: + dest.append('') + elif ln.startswith('#'): + title = ' '.join(toks[1:]) + dest.append(title) + dest.append('-' * len(title)) + elif toks and toks[0] == '*': + line = '- ' + ln[1:] + dest.append(line) + elif ln.startswith(' '): + if prev == 'code': + dest.append(ln) + else: + dest.append('::') + dest.append('') + dest.append(ln) + curr = 'code' + else: + dest.append(ln) + + # done + dest.append('') + readme = os.path.join(source_path, "README") + with open(readme, 'w') as f: + f.write('\n'.join(dest)) + + print "README was written" diff --git a/src/build_utils/virtualenv/LICENSE.txt b/src/build_utils/virtualenv/LICENSE.txt new file mode 100644 index 0000000000..7e00d5d512 --- /dev/null +++ b/src/build_utils/virtualenv/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2007 Ian Bicking and Contributors +Copyright (c) 2009 Ian Bicking, The Open Planning Project +Copyright (c) 2011-2014 The virtualenv developers + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/build_utils/virtualenv/__init__.py b/src/build_utils/virtualenv/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/build_utils/virtualenv/virtualenv.py b/src/build_utils/virtualenv/virtualenv.py new file mode 100755 index 0000000000..0329fc98c0 --- /dev/null +++ b/src/build_utils/virtualenv/virtualenv.py @@ -0,0 +1,2342 @@ +#!/usr/bin/env python +"""Create a "virtual" Python installation +""" + +__version__ = "1.11.6" +virtualenv_version = __version__ # legacy + +import base64 +import sys +import os +import codecs +import optparse +import re +import shutil +import logging +import tempfile +import zlib +import errno +import glob +import distutils.sysconfig +from distutils.util import strtobool +import struct +import subprocess +import tarfile + +if sys.version_info < (2, 6): + print('ERROR: %s' % sys.exc_info()[1]) + print('ERROR: this script requires Python 2.6 or greater.') + sys.exit(101) + +try: + set +except NameError: + from sets import Set as set +try: + basestring +except NameError: + basestring = str + +try: + import ConfigParser +except ImportError: + import configparser as ConfigParser + +join = os.path.join +py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) + +is_jython = sys.platform.startswith('java') +is_pypy = hasattr(sys, 'pypy_version_info') +is_win = (sys.platform == 'win32') +is_cygwin = (sys.platform == 'cygwin') +is_darwin = (sys.platform == 'darwin') +abiflags = getattr(sys, 'abiflags', '') + +user_dir = os.path.expanduser('~') +if is_win: + default_storage_dir = os.path.join(user_dir, 'virtualenv') +else: + default_storage_dir = os.path.join(user_dir, '.virtualenv') +default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini') + +if is_pypy: + expected_exe = 'pypy' +elif is_jython: + expected_exe = 'jython' +else: + expected_exe = 'python' + +# Return a mapping of version -> Python executable +# Only provided for Windows, where the information in the registry is used +if not is_win: + def get_installed_pythons(): + return {} +else: + try: + import winreg + except ImportError: + import _winreg as winreg + + def get_installed_pythons(): + python_core = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, + "Software\\Python\\PythonCore") + i = 0 + versions = [] + while True: + try: + versions.append(winreg.EnumKey(python_core, i)) + i = i + 1 + except WindowsError: + break + exes = dict() + for ver in versions: + path = winreg.QueryValue(python_core, "%s\\InstallPath" % ver) + exes[ver] = join(path, "python.exe") + + winreg.CloseKey(python_core) + + # Add the major versions + # Sort the keys, then repeatedly update the major version entry + # Last executable (i.e., highest version) wins with this approach + for ver in sorted(exes): + exes[ver[0]] = exes[ver] + + return exes + +REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', + 'fnmatch', 'locale', 'encodings', 'codecs', + 'stat', 'UserDict', 'readline', 'copy_reg', 'types', + 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', + 'zlib'] + +REQUIRED_FILES = ['lib-dynload', 'config'] + +majver, minver = sys.version_info[:2] +if majver == 2: + if minver >= 6: + REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) + if minver >= 7: + REQUIRED_MODULES.extend(['_weakrefset']) + if minver <= 3: + REQUIRED_MODULES.extend(['sets', '__future__']) +elif majver == 3: + # Some extra modules are needed for Python 3, but different ones + # for different versions. + REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', + '_weakrefset', 'copyreg', 'tempfile', 'random', + '__future__', 'collections', 'keyword', 'tarfile', + 'shutil', 'struct', 'copy', 'tokenize', 'token', + 'functools', 'heapq', 'bisect', 'weakref', + 'reprlib']) + if minver >= 2: + REQUIRED_FILES[-1] = 'config-%s' % majver + if minver >= 3: + import sysconfig + platdir = sysconfig.get_config_var('PLATDIR') + REQUIRED_FILES.append(platdir) + # The whole list of 3.3 modules is reproduced below - the current + # uncommented ones are required for 3.3 as of now, but more may be + # added as 3.3 development continues. + REQUIRED_MODULES.extend([ + #"aifc", + #"antigravity", + #"argparse", + #"ast", + #"asynchat", + #"asyncore", + "base64", + #"bdb", + #"binhex", + #"bisect", + #"calendar", + #"cgi", + #"cgitb", + #"chunk", + #"cmd", + #"codeop", + #"code", + #"colorsys", + #"_compat_pickle", + #"compileall", + #"concurrent", + #"configparser", + #"contextlib", + #"cProfile", + #"crypt", + #"csv", + #"ctypes", + #"curses", + #"datetime", + #"dbm", + #"decimal", + #"difflib", + #"dis", + #"doctest", + #"dummy_threading", + "_dummy_thread", + #"email", + #"filecmp", + #"fileinput", + #"formatter", + #"fractions", + #"ftplib", + #"functools", + #"getopt", + #"getpass", + #"gettext", + #"glob", + #"gzip", + "hashlib", + #"heapq", + "hmac", + #"html", + #"http", + #"idlelib", + #"imaplib", + #"imghdr", + "imp", + "importlib", + #"inspect", + #"json", + #"lib2to3", + #"logging", + #"macpath", + #"macurl2path", + #"mailbox", + #"mailcap", + #"_markupbase", + #"mimetypes", + #"modulefinder", + #"multiprocessing", + #"netrc", + #"nntplib", + #"nturl2path", + #"numbers", + #"opcode", + #"optparse", + #"os2emxpath", + #"pdb", + #"pickle", + #"pickletools", + #"pipes", + #"pkgutil", + #"platform", + #"plat-linux2", + #"plistlib", + #"poplib", + #"pprint", + #"profile", + #"pstats", + #"pty", + #"pyclbr", + #"py_compile", + #"pydoc_data", + #"pydoc", + #"_pyio", + #"queue", + #"quopri", + #"reprlib", + "rlcompleter", + #"runpy", + #"sched", + #"shelve", + #"shlex", + #"smtpd", + #"smtplib", + #"sndhdr", + #"socket", + #"socketserver", + #"sqlite3", + #"ssl", + #"stringprep", + #"string", + #"_strptime", + #"subprocess", + #"sunau", + #"symbol", + #"symtable", + #"sysconfig", + #"tabnanny", + #"telnetlib", + #"test", + #"textwrap", + #"this", + #"_threading_local", + #"threading", + #"timeit", + #"tkinter", + #"tokenize", + #"token", + #"traceback", + #"trace", + #"tty", + #"turtledemo", + #"turtle", + #"unittest", + #"urllib", + #"uuid", + #"uu", + #"wave", + #"weakref", + #"webbrowser", + #"wsgiref", + #"xdrlib", + #"xml", + #"xmlrpc", + #"zipfile", + ]) + if minver >= 4: + REQUIRED_MODULES.extend([ + 'operator', + '_collections_abc', + '_bootlocale', + ]) + +if is_pypy: + # these are needed to correctly display the exceptions that may happen + # during the bootstrap + REQUIRED_MODULES.extend(['traceback', 'linecache']) + +class Logger(object): + + """ + Logging object for use in command-line script. Allows ranges of + levels, to avoid some redundancy of displayed information. + """ + + DEBUG = logging.DEBUG + INFO = logging.INFO + NOTIFY = (logging.INFO+logging.WARN)/2 + WARN = WARNING = logging.WARN + ERROR = logging.ERROR + FATAL = logging.FATAL + + LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] + + def __init__(self, consumers): + self.consumers = consumers + self.indent = 0 + self.in_progress = None + self.in_progress_hanging = False + + def debug(self, msg, *args, **kw): + self.log(self.DEBUG, msg, *args, **kw) + def info(self, msg, *args, **kw): + self.log(self.INFO, msg, *args, **kw) + def notify(self, msg, *args, **kw): + self.log(self.NOTIFY, msg, *args, **kw) + def warn(self, msg, *args, **kw): + self.log(self.WARN, msg, *args, **kw) + def error(self, msg, *args, **kw): + self.log(self.ERROR, msg, *args, **kw) + def fatal(self, msg, *args, **kw): + self.log(self.FATAL, msg, *args, **kw) + def log(self, level, msg, *args, **kw): + if args: + if kw: + raise TypeError( + "You may give positional or keyword arguments, not both") + args = args or kw + rendered = None + for consumer_level, consumer in self.consumers: + if self.level_matches(level, consumer_level): + if (self.in_progress_hanging + and consumer in (sys.stdout, sys.stderr)): + self.in_progress_hanging = False + sys.stdout.write('\n') + sys.stdout.flush() + if rendered is None: + if args: + rendered = msg % args + else: + rendered = msg + rendered = ' '*self.indent + rendered + if hasattr(consumer, 'write'): + consumer.write(rendered+'\n') + else: + consumer(rendered) + + def start_progress(self, msg): + assert not self.in_progress, ( + "Tried to start_progress(%r) while in_progress %r" + % (msg, self.in_progress)) + if self.level_matches(self.NOTIFY, self._stdout_level()): + sys.stdout.write(msg) + sys.stdout.flush() + self.in_progress_hanging = True + else: + self.in_progress_hanging = False + self.in_progress = msg + + def end_progress(self, msg='done.'): + assert self.in_progress, ( + "Tried to end_progress without start_progress") + if self.stdout_level_matches(self.NOTIFY): + if not self.in_progress_hanging: + # Some message has been printed out since start_progress + sys.stdout.write('...' + self.in_progress + msg + '\n') + sys.stdout.flush() + else: + sys.stdout.write(msg + '\n') + sys.stdout.flush() + self.in_progress = None + self.in_progress_hanging = False + + def show_progress(self): + """If we are in a progress scope, and no log messages have been + shown, write out another '.'""" + if self.in_progress_hanging: + sys.stdout.write('.') + sys.stdout.flush() + + def stdout_level_matches(self, level): + """Returns true if a message at this level will go to stdout""" + return self.level_matches(level, self._stdout_level()) + + def _stdout_level(self): + """Returns the level that stdout runs at""" + for level, consumer in self.consumers: + if consumer is sys.stdout: + return level + return self.FATAL + + def level_matches(self, level, consumer_level): + """ + >>> l = Logger([]) + >>> l.level_matches(3, 4) + False + >>> l.level_matches(3, 2) + True + >>> l.level_matches(slice(None, 3), 3) + False + >>> l.level_matches(slice(None, 3), 2) + True + >>> l.level_matches(slice(1, 3), 1) + True + >>> l.level_matches(slice(2, 3), 1) + False + """ + if isinstance(level, slice): + start, stop = level.start, level.stop + if start is not None and start > consumer_level: + return False + if stop is not None and stop <= consumer_level: + return False + return True + else: + return level >= consumer_level + + #@classmethod + def level_for_integer(cls, level): + levels = cls.LEVELS + if level < 0: + return levels[0] + if level >= len(levels): + return levels[-1] + return levels[level] + + level_for_integer = classmethod(level_for_integer) + +# create a silent logger just to prevent this from being undefined +# will be overridden with requested verbosity main() is called. +logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) + +def mkdir(path): + if not os.path.exists(path): + logger.info('Creating %s', path) + os.makedirs(path) + else: + logger.info('Directory %s already exists', path) + +def copyfileordir(src, dest, symlink=True): + if os.path.isdir(src): + shutil.copytree(src, dest, symlink) + else: + shutil.copy2(src, dest) + +def copyfile(src, dest, symlink=True): + if not os.path.exists(src): + # Some bad symlink in the src + logger.warn('Cannot find file %s (bad symlink)', src) + return + if os.path.exists(dest): + logger.debug('File %s already exists', dest) + return + if not os.path.exists(os.path.dirname(dest)): + logger.info('Creating parent directories for %s', os.path.dirname(dest)) + os.makedirs(os.path.dirname(dest)) + if not os.path.islink(src): + srcpath = os.path.abspath(src) + else: + srcpath = os.readlink(src) + if symlink and hasattr(os, 'symlink') and not is_win: + logger.info('Symlinking %s', dest) + try: + os.symlink(srcpath, dest) + except (OSError, NotImplementedError): + logger.info('Symlinking failed, copying to %s', dest) + copyfileordir(src, dest, symlink) + else: + logger.info('Copying to %s', dest) + copyfileordir(src, dest, symlink) + +def writefile(dest, content, overwrite=True): + if not os.path.exists(dest): + logger.info('Writing %s', dest) + f = open(dest, 'wb') + f.write(content.encode('utf-8')) + f.close() + return + else: + f = open(dest, 'rb') + c = f.read() + f.close() + if c != content.encode("utf-8"): + if not overwrite: + logger.notify('File %s exists with different content; not overwriting', dest) + return + logger.notify('Overwriting %s with new content', dest) + f = open(dest, 'wb') + f.write(content.encode('utf-8')) + f.close() + else: + logger.info('Content %s already in place', dest) + +def rmtree(dir): + if os.path.exists(dir): + logger.notify('Deleting tree %s', dir) + shutil.rmtree(dir) + else: + logger.info('Do not need to delete %s; already gone', dir) + +def make_exe(fn): + if hasattr(os, 'chmod'): + oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 + newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 + os.chmod(fn, newmode) + logger.info('Changed mode of %s to %s', fn, oct(newmode)) + +def _find_file(filename, dirs): + for dir in reversed(dirs): + files = glob.glob(os.path.join(dir, filename)) + if files and os.path.isfile(files[0]): + return True, files[0] + return False, filename + +def file_search_dirs(): + here = os.path.dirname(os.path.abspath(__file__)) + dirs = ['.', here, + join(here, 'virtualenv_support')] + if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': + # Probably some boot script; just in case virtualenv is installed... + try: + import virtualenv + except ImportError: + pass + else: + dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) + return [d for d in dirs if os.path.isdir(d)] + + +class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): + """ + Custom help formatter for use in ConfigOptionParser that updates + the defaults before expanding them, allowing them to show up correctly + in the help listing + """ + def expand_default(self, option): + if self.parser is not None: + self.parser.update_defaults(self.parser.defaults) + return optparse.IndentedHelpFormatter.expand_default(self, option) + + +class ConfigOptionParser(optparse.OptionParser): + """ + Custom option parser which updates its defaults by checking the + configuration files and environmental variables + """ + def __init__(self, *args, **kwargs): + self.config = ConfigParser.RawConfigParser() + self.files = self.get_config_files() + self.config.read(self.files) + optparse.OptionParser.__init__(self, *args, **kwargs) + + def get_config_files(self): + config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False) + if config_file and os.path.exists(config_file): + return [config_file] + return [default_config_file] + + def update_defaults(self, defaults): + """ + Updates the given defaults with values from the config files and + the environ. Does a little special handling for certain types of + options (lists). + """ + # Then go and look for the other sources of configuration: + config = {} + # 1. config files + config.update(dict(self.get_config_section('virtualenv'))) + # 2. environmental variables + config.update(dict(self.get_environ_vars())) + # Then set the options with those values + for key, val in config.items(): + key = key.replace('_', '-') + if not key.startswith('--'): + key = '--%s' % key # only prefer long opts + option = self.get_option(key) + if option is not None: + # ignore empty values + if not val: + continue + # handle multiline configs + if option.action == 'append': + val = val.split() + else: + option.nargs = 1 + if option.action == 'store_false': + val = not strtobool(val) + elif option.action in ('store_true', 'count'): + val = strtobool(val) + try: + val = option.convert_value(key, val) + except optparse.OptionValueError: + e = sys.exc_info()[1] + print("An error occured during configuration: %s" % e) + sys.exit(3) + defaults[option.dest] = val + return defaults + + def get_config_section(self, name): + """ + Get a section of a configuration + """ + if self.config.has_section(name): + return self.config.items(name) + return [] + + def get_environ_vars(self, prefix='VIRTUALENV_'): + """ + Returns a generator with all environmental vars with prefix VIRTUALENV + """ + for key, val in os.environ.items(): + if key.startswith(prefix): + yield (key.replace(prefix, '').lower(), val) + + def get_default_values(self): + """ + Overridding to make updating the defaults after instantiation of + the option parser possible, update_defaults() does the dirty work. + """ + if not self.process_default_values: + # Old, pre-Optik 1.5 behaviour. + return optparse.Values(self.defaults) + + defaults = self.update_defaults(self.defaults.copy()) # ours + for option in self._get_all_options(): + default = defaults.get(option.dest) + if isinstance(default, basestring): + opt_str = option.get_opt_string() + defaults[option.dest] = option.check_value(opt_str, default) + return optparse.Values(defaults) + + +def main(): + parser = ConfigOptionParser( + version=virtualenv_version, + usage="%prog [OPTIONS] DEST_DIR", + formatter=UpdatingDefaultsHelpFormatter()) + + parser.add_option( + '-v', '--verbose', + action='count', + dest='verbose', + default=0, + help="Increase verbosity.") + + parser.add_option( + '-q', '--quiet', + action='count', + dest='quiet', + default=0, + help='Decrease verbosity.') + + parser.add_option( + '-p', '--python', + dest='python', + metavar='PYTHON_EXE', + help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' + 'interpreter to create the new environment. The default is the interpreter that ' + 'virtualenv was installed with (%s)' % sys.executable) + + parser.add_option( + '--clear', + dest='clear', + action='store_true', + help="Clear out the non-root install and start from scratch.") + + parser.set_defaults(system_site_packages=False) + parser.add_option( + '--no-site-packages', + dest='system_site_packages', + action='store_false', + help="DEPRECATED. Retained only for backward compatibility. " + "Not having access to global site-packages is now the default behavior.") + + parser.add_option( + '--system-site-packages', + dest='system_site_packages', + action='store_true', + help="Give the virtual environment access to the global site-packages.") + + parser.add_option( + '--always-copy', + dest='symlink', + action='store_false', + default=True, + help="Always copy files rather than symlinking.") + + parser.add_option( + '--unzip-setuptools', + dest='unzip_setuptools', + action='store_true', + help="Unzip Setuptools when installing it.") + + parser.add_option( + '--relocatable', + dest='relocatable', + action='store_true', + help='Make an EXISTING virtualenv environment relocatable. ' + 'This fixes up scripts and makes all .pth files relative.') + + parser.add_option( + '--no-setuptools', + dest='no_setuptools', + action='store_true', + help='Do not install setuptools (or pip) in the new virtualenv.') + + parser.add_option( + '--no-pip', + dest='no_pip', + action='store_true', + help='Do not install pip in the new virtualenv.') + + default_search_dirs = file_search_dirs() + parser.add_option( + '--extra-search-dir', + dest="search_dirs", + action="append", + metavar='DIR', + default=default_search_dirs, + help="Directory to look for setuptools/pip distributions in. " + "This option can be used multiple times.") + + parser.add_option( + '--never-download', + dest="never_download", + action="store_true", + default=True, + help="DEPRECATED. Retained only for backward compatibility. This option has no effect. " + "Virtualenv never downloads pip or setuptools.") + + parser.add_option( + '--prompt', + dest='prompt', + help='Provides an alternative prompt prefix for this environment.') + + parser.add_option( + '--setuptools', + dest='setuptools', + action='store_true', + help="DEPRECATED. Retained only for backward compatibility. This option has no effect.") + + parser.add_option( + '--distribute', + dest='distribute', + action='store_true', + help="DEPRECATED. Retained only for backward compatibility. This option has no effect.") + + if 'extend_parser' in globals(): + extend_parser(parser) + + options, args = parser.parse_args() + + global logger + + if 'adjust_options' in globals(): + adjust_options(options, args) + + verbosity = options.verbose - options.quiet + logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)]) + + if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): + env = os.environ.copy() + interpreter = resolve_interpreter(options.python) + if interpreter == sys.executable: + logger.warn('Already using interpreter %s' % interpreter) + else: + logger.notify('Running virtualenv with interpreter %s' % interpreter) + env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' + file = __file__ + if file.endswith('.pyc'): + file = file[:-1] + popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) + raise SystemExit(popen.wait()) + + if not args: + print('You must provide a DEST_DIR') + parser.print_help() + sys.exit(2) + if len(args) > 1: + print('There must be only one argument: DEST_DIR (you gave %s)' % ( + ' '.join(args))) + parser.print_help() + sys.exit(2) + + home_dir = args[0] + + if os.environ.get('WORKING_ENV'): + logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') + logger.fatal('Please deactivate your workingenv, then re-run this script') + sys.exit(3) + + if 'PYTHONHOME' in os.environ: + logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') + del os.environ['PYTHONHOME'] + + if options.relocatable: + make_environment_relocatable(home_dir) + return + + if not options.never_download: + logger.warn('The --never-download option is for backward compatibility only.') + logger.warn('Setting it to false is no longer supported, and will be ignored.') + + create_environment(home_dir, + site_packages=options.system_site_packages, + clear=options.clear, + unzip_setuptools=options.unzip_setuptools, + prompt=options.prompt, + search_dirs=options.search_dirs, + never_download=True, + no_setuptools=options.no_setuptools, + no_pip=options.no_pip, + symlink=options.symlink) + if 'after_install' in globals(): + after_install(options, home_dir) + +def call_subprocess(cmd, show_stdout=True, + filter_stdout=None, cwd=None, + raise_on_returncode=True, extra_env=None, + remove_from_env=None): + cmd_parts = [] + for part in cmd: + if len(part) > 45: + part = part[:20]+"..."+part[-20:] + if ' ' in part or '\n' in part or '"' in part or "'" in part: + part = '"%s"' % part.replace('"', '\\"') + if hasattr(part, 'decode'): + try: + part = part.decode(sys.getdefaultencoding()) + except UnicodeDecodeError: + part = part.decode(sys.getfilesystemencoding()) + cmd_parts.append(part) + cmd_desc = ' '.join(cmd_parts) + if show_stdout: + stdout = None + else: + stdout = subprocess.PIPE + logger.debug("Running command %s" % cmd_desc) + if extra_env or remove_from_env: + env = os.environ.copy() + if extra_env: + env.update(extra_env) + if remove_from_env: + for varname in remove_from_env: + env.pop(varname, None) + else: + env = None + try: + proc = subprocess.Popen( + cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, + cwd=cwd, env=env) + except Exception: + e = sys.exc_info()[1] + logger.fatal( + "Error %s while executing command %s" % (e, cmd_desc)) + raise + all_output = [] + if stdout is not None: + stdout = proc.stdout + encoding = sys.getdefaultencoding() + fs_encoding = sys.getfilesystemencoding() + while 1: + line = stdout.readline() + try: + line = line.decode(encoding) + except UnicodeDecodeError: + line = line.decode(fs_encoding) + if not line: + break + line = line.rstrip() + all_output.append(line) + if filter_stdout: + level = filter_stdout(line) + if isinstance(level, tuple): + level, line = level + logger.log(level, line) + if not logger.stdout_level_matches(level): + logger.show_progress() + else: + logger.info(line) + else: + proc.communicate() + proc.wait() + if proc.returncode: + if raise_on_returncode: + if all_output: + logger.notify('Complete output from command %s:' % cmd_desc) + logger.notify('\n'.join(all_output) + '\n----------------------------------------') + raise OSError( + "Command %s failed with error code %s" + % (cmd_desc, proc.returncode)) + else: + logger.warn( + "Command %s had error code %s" + % (cmd_desc, proc.returncode)) + +def filter_install_output(line): + if line.strip().startswith('running'): + return Logger.INFO + return Logger.DEBUG + +def find_wheels(projects, search_dirs): + """Find wheels from which we can import PROJECTS. + + Scan through SEARCH_DIRS for a wheel for each PROJECT in turn. Return + a list of the first wheel found for each PROJECT + """ + + wheels = [] + + # Look through SEARCH_DIRS for the first suitable wheel. Don't bother + # about version checking here, as this is simply to get something we can + # then use to install the correct version. + for project in projects: + for dirname in search_dirs: + # This relies on only having "universal" wheels available. + # The pattern could be tightened to require -py2.py3-none-any.whl. + files = glob.glob(os.path.join(dirname, project + '-*.whl')) + if files: + wheels.append(os.path.abspath(files[0])) + break + else: + # We're out of luck, so quit with a suitable error + logger.fatal('Cannot find a wheel for %s' % (project,)) + + return wheels + +def install_wheel(project_names, py_executable, search_dirs=None): + if search_dirs is None: + search_dirs = file_search_dirs() + + wheels = find_wheels(['setuptools', 'pip'], search_dirs) + pythonpath = os.pathsep.join(wheels) + findlinks = ' '.join(search_dirs) + + cmd = [ + py_executable, '-c', + 'import sys, pip; sys.exit(pip.main(["install", "--ignore-installed"] + sys.argv[1:]))', + ] + project_names + logger.start_progress('Installing %s...' % (', '.join(project_names))) + logger.indent += 2 + try: + call_subprocess(cmd, show_stdout=False, + extra_env = { + 'PYTHONPATH': pythonpath, + 'PIP_FIND_LINKS': findlinks, + 'PIP_USE_WHEEL': '1', + 'PIP_PRE': '1', + 'PIP_NO_INDEX': '1' + } + ) + finally: + logger.indent -= 2 + logger.end_progress() + +def create_environment(home_dir, site_packages=False, clear=False, + unzip_setuptools=False, + prompt=None, search_dirs=None, never_download=False, + no_setuptools=False, no_pip=False, symlink=True): + """ + Creates a new environment in ``home_dir``. + + If ``site_packages`` is true, then the global ``site-packages/`` + directory will be on the path. + + If ``clear`` is true (default False) then the environment will + first be cleared. + """ + home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) + + py_executable = os.path.abspath(install_python( + home_dir, lib_dir, inc_dir, bin_dir, + site_packages=site_packages, clear=clear, symlink=symlink)) + + install_distutils(home_dir) + + if not no_setuptools: + to_install = ['setuptools'] + if not no_pip: + to_install.append('pip') + install_wheel(to_install, py_executable, search_dirs) + + install_activate(home_dir, bin_dir, prompt) + +def is_executable_file(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def path_locations(home_dir): + """Return the path locations for the environment (where libraries are, + where scripts go, etc)""" + # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its + # prefix arg is broken: http://bugs.python.org/issue3386 + if is_win: + # Windows has lots of problems with executables with spaces in + # the name; this function will remove them (using the ~1 + # format): + mkdir(home_dir) + if ' ' in home_dir: + import ctypes + GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW + size = max(len(home_dir)+1, 256) + buf = ctypes.create_unicode_buffer(size) + try: + u = unicode + except NameError: + u = str + ret = GetShortPathName(u(home_dir), buf, size) + if not ret: + print('Error: the path "%s" has a space in it' % home_dir) + print('We could not determine the short pathname for it.') + print('Exiting.') + sys.exit(3) + home_dir = str(buf.value) + lib_dir = join(home_dir, 'Lib') + inc_dir = join(home_dir, 'Include') + bin_dir = join(home_dir, 'Scripts') + if is_jython: + lib_dir = join(home_dir, 'Lib') + inc_dir = join(home_dir, 'Include') + bin_dir = join(home_dir, 'bin') + elif is_pypy: + lib_dir = home_dir + inc_dir = join(home_dir, 'include') + bin_dir = join(home_dir, 'bin') + elif not is_win: + lib_dir = join(home_dir, 'lib', py_version) + multiarch_exec = '/usr/bin/multiarch-platform' + if is_executable_file(multiarch_exec): + # In Mageia (2) and Mandriva distros the include dir must be like: + # virtualenv/include/multiarch-x86_64-linux/python2.7 + # instead of being virtualenv/include/python2.7 + p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + # stdout.strip is needed to remove newline character + inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags) + else: + inc_dir = join(home_dir, 'include', py_version + abiflags) + bin_dir = join(home_dir, 'bin') + return home_dir, lib_dir, inc_dir, bin_dir + + +def change_prefix(filename, dst_prefix): + prefixes = [sys.prefix] + + if is_darwin: + prefixes.extend(( + os.path.join("/Library/Python", sys.version[:3], "site-packages"), + os.path.join(sys.prefix, "Extras", "lib", "python"), + os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"), + # Python 2.6 no-frameworks + os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"), + # System Python 2.7 on OSX Mountain Lion + os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages"))) + + if hasattr(sys, 'real_prefix'): + prefixes.append(sys.real_prefix) + if hasattr(sys, 'base_prefix'): + prefixes.append(sys.base_prefix) + prefixes = list(map(os.path.expanduser, prefixes)) + prefixes = list(map(os.path.abspath, prefixes)) + # Check longer prefixes first so we don't split in the middle of a filename + prefixes = sorted(prefixes, key=len, reverse=True) + filename = os.path.abspath(filename) + for src_prefix in prefixes: + if filename.startswith(src_prefix): + _, relpath = filename.split(src_prefix, 1) + if src_prefix != os.sep: # sys.prefix == "/" + assert relpath[0] == os.sep + relpath = relpath[1:] + return join(dst_prefix, relpath) + assert False, "Filename %s does not start with any of these prefixes: %s" % \ + (filename, prefixes) + +def copy_required_modules(dst_prefix, symlink): + import imp + # If we are running under -p, we need to remove the current + # directory from sys.path temporarily here, so that we + # definitely get the modules from the site directory of + # the interpreter we are running under, not the one + # virtualenv.py is installed under (which might lead to py2/py3 + # incompatibility issues) + _prev_sys_path = sys.path + if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): + sys.path = sys.path[1:] + try: + for modname in REQUIRED_MODULES: + if modname in sys.builtin_module_names: + logger.info("Ignoring built-in bootstrap module: %s" % modname) + continue + try: + f, filename, _ = imp.find_module(modname) + except ImportError: + logger.info("Cannot import bootstrap module: %s" % modname) + else: + if f is not None: + f.close() + # special-case custom readline.so on OS X, but not for pypy: + if modname == 'readline' and sys.platform == 'darwin' and not ( + is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))): + dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so') + elif modname == 'readline' and sys.platform == 'win32': + # special-case for Windows, where readline is not a + # standard module, though it may have been installed in + # site-packages by a third-party package + pass + else: + dst_filename = change_prefix(filename, dst_prefix) + copyfile(filename, dst_filename, symlink) + if filename.endswith('.pyc'): + pyfile = filename[:-1] + if os.path.exists(pyfile): + copyfile(pyfile, dst_filename[:-1], symlink) + finally: + sys.path = _prev_sys_path + + +def subst_path(prefix_path, prefix, home_dir): + prefix_path = os.path.normpath(prefix_path) + prefix = os.path.normpath(prefix) + home_dir = os.path.normpath(home_dir) + if not prefix_path.startswith(prefix): + logger.warn('Path not in prefix %r %r', prefix_path, prefix) + return + return prefix_path.replace(prefix, home_dir, 1) + + +def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear, symlink=True): + """Install just the base environment, no distutils patches etc""" + if sys.executable.startswith(bin_dir): + print('Please use the *system* python to run this script') + return + + if clear: + rmtree(lib_dir) + ## FIXME: why not delete it? + ## Maybe it should delete everything with #!/path/to/venv/python in it + logger.notify('Not deleting %s', bin_dir) + + if hasattr(sys, 'real_prefix'): + logger.notify('Using real prefix %r' % sys.real_prefix) + prefix = sys.real_prefix + elif hasattr(sys, 'base_prefix'): + logger.notify('Using base prefix %r' % sys.base_prefix) + prefix = sys.base_prefix + else: + prefix = sys.prefix + mkdir(lib_dir) + fix_lib64(lib_dir, symlink) + stdlib_dirs = [os.path.dirname(os.__file__)] + if is_win: + stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) + elif is_darwin: + stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) + if hasattr(os, 'symlink'): + logger.info('Symlinking Python bootstrap modules') + else: + logger.info('Copying Python bootstrap modules') + logger.indent += 2 + try: + # copy required files... + for stdlib_dir in stdlib_dirs: + if not os.path.isdir(stdlib_dir): + continue + for fn in os.listdir(stdlib_dir): + bn = os.path.splitext(fn)[0] + if fn != 'site-packages' and bn in REQUIRED_FILES: + copyfile(join(stdlib_dir, fn), join(lib_dir, fn), symlink) + # ...and modules + copy_required_modules(home_dir, symlink) + finally: + logger.indent -= 2 + mkdir(join(lib_dir, 'site-packages')) + import site + site_filename = site.__file__ + if site_filename.endswith('.pyc'): + site_filename = site_filename[:-1] + elif site_filename.endswith('$py.class'): + site_filename = site_filename.replace('$py.class', '.py') + site_filename_dst = change_prefix(site_filename, home_dir) + site_dir = os.path.dirname(site_filename_dst) + writefile(site_filename_dst, SITE_PY) + writefile(join(site_dir, 'orig-prefix.txt'), prefix) + site_packages_filename = join(site_dir, 'no-global-site-packages.txt') + if not site_packages: + writefile(site_packages_filename, '') + + if is_pypy or is_win: + stdinc_dir = join(prefix, 'include') + else: + stdinc_dir = join(prefix, 'include', py_version + abiflags) + if os.path.exists(stdinc_dir): + copyfile(stdinc_dir, inc_dir, symlink) + else: + logger.debug('No include dir %s' % stdinc_dir) + + platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1) + if platinc_dir != stdinc_dir: + platinc_dest = distutils.sysconfig.get_python_inc( + plat_specific=1, prefix=home_dir) + if platinc_dir == platinc_dest: + # Do platinc_dest manually due to a CPython bug; + # not http://bugs.python.org/issue3386 but a close cousin + platinc_dest = subst_path(platinc_dir, prefix, home_dir) + if platinc_dest: + # PyPy's stdinc_dir and prefix are relative to the original binary + # (traversing virtualenvs), whereas the platinc_dir is relative to + # the inner virtualenv and ignores the prefix argument. + # This seems more evolved than designed. + copyfile(platinc_dir, platinc_dest, symlink) + + # pypy never uses exec_prefix, just ignore it + if sys.exec_prefix != prefix and not is_pypy: + if is_win: + exec_dir = join(sys.exec_prefix, 'lib') + elif is_jython: + exec_dir = join(sys.exec_prefix, 'Lib') + else: + exec_dir = join(sys.exec_prefix, 'lib', py_version) + for fn in os.listdir(exec_dir): + copyfile(join(exec_dir, fn), join(lib_dir, fn), symlink) + + if is_jython: + # Jython has either jython-dev.jar and javalib/ dir, or just + # jython.jar + for name in 'jython-dev.jar', 'javalib', 'jython.jar': + src = join(prefix, name) + if os.path.exists(src): + copyfile(src, join(home_dir, name), symlink) + # XXX: registry should always exist after Jython 2.5rc1 + src = join(prefix, 'registry') + if os.path.exists(src): + copyfile(src, join(home_dir, 'registry'), symlink=False) + copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), + symlink=False) + + mkdir(bin_dir) + py_executable = join(bin_dir, os.path.basename(sys.executable)) + if 'Python.framework' in prefix: + # OS X framework builds cause validation to break + # https://github.com/pypa/virtualenv/issues/322 + if os.environ.get('__PYVENV_LAUNCHER__'): + del os.environ["__PYVENV_LAUNCHER__"] + if re.search(r'/Python(?:-32|-64)*$', py_executable): + # The name of the python executable is not quite what + # we want, rename it. + py_executable = os.path.join( + os.path.dirname(py_executable), 'python') + + logger.notify('New %s executable in %s', expected_exe, py_executable) + pcbuild_dir = os.path.dirname(sys.executable) + pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth') + if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')): + logger.notify('Detected python running from build directory %s', pcbuild_dir) + logger.notify('Writing .pth file linking to build directory for *.pyd files') + writefile(pyd_pth, pcbuild_dir) + else: + pcbuild_dir = None + if os.path.exists(pyd_pth): + logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth) + os.unlink(pyd_pth) + + if sys.executable != py_executable: + ## FIXME: could I just hard link? + executable = sys.executable + shutil.copyfile(executable, py_executable) + make_exe(py_executable) + if is_win or is_cygwin: + pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') + if os.path.exists(pythonw): + logger.info('Also created pythonw.exe') + shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) + python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe') + python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe') + if os.path.exists(python_d): + logger.info('Also created python_d.exe') + shutil.copyfile(python_d, python_d_dest) + elif os.path.exists(python_d_dest): + logger.info('Removed python_d.exe as it is no longer at the source') + os.unlink(python_d_dest) + # we need to copy the DLL to enforce that windows will load the correct one. + # may not exist if we are cygwin. + py_executable_dll = 'python%s%s.dll' % ( + sys.version_info[0], sys.version_info[1]) + py_executable_dll_d = 'python%s%s_d.dll' % ( + sys.version_info[0], sys.version_info[1]) + pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) + pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d) + pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d) + if os.path.exists(pythondll): + logger.info('Also created %s' % py_executable_dll) + shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) + if os.path.exists(pythondll_d): + logger.info('Also created %s' % py_executable_dll_d) + shutil.copyfile(pythondll_d, pythondll_d_dest) + elif os.path.exists(pythondll_d_dest): + logger.info('Removed %s as the source does not exist' % pythondll_d_dest) + os.unlink(pythondll_d_dest) + if is_pypy: + # make a symlink python --> pypy-c + python_executable = os.path.join(os.path.dirname(py_executable), 'python') + if sys.platform in ('win32', 'cygwin'): + python_executable += '.exe' + logger.info('Also created executable %s' % python_executable) + copyfile(py_executable, python_executable, symlink) + + if is_win: + for name in 'libexpat.dll', 'libpypy.dll', 'libpypy-c.dll', 'libeay32.dll', 'ssleay32.dll', 'sqlite.dll': + src = join(prefix, name) + if os.path.exists(src): + copyfile(src, join(bin_dir, name), symlink) + + if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: + secondary_exe = os.path.join(os.path.dirname(py_executable), + expected_exe) + py_executable_ext = os.path.splitext(py_executable)[1] + if py_executable_ext.lower() == '.exe': + # python2.4 gives an extension of '.4' :P + secondary_exe += py_executable_ext + if os.path.exists(secondary_exe): + logger.warn('Not overwriting existing %s script %s (you must use %s)' + % (expected_exe, secondary_exe, py_executable)) + else: + logger.notify('Also creating executable in %s' % secondary_exe) + shutil.copyfile(sys.executable, secondary_exe) + make_exe(secondary_exe) + + if '.framework' in prefix: + if 'Python.framework' in prefix: + logger.debug('MacOSX Python framework detected') + # Make sure we use the the embedded interpreter inside + # the framework, even if sys.executable points to + # the stub executable in ${sys.prefix}/bin + # See http://groups.google.com/group/python-virtualenv/ + # browse_thread/thread/17cab2f85da75951 + original_python = os.path.join( + prefix, 'Resources/Python.app/Contents/MacOS/Python') + if 'EPD' in prefix: + logger.debug('EPD framework detected') + original_python = os.path.join(prefix, 'bin/python') + shutil.copy(original_python, py_executable) + + # Copy the framework's dylib into the virtual + # environment + virtual_lib = os.path.join(home_dir, '.Python') + + if os.path.exists(virtual_lib): + os.unlink(virtual_lib) + copyfile( + os.path.join(prefix, 'Python'), + virtual_lib, + symlink) + + # And then change the install_name of the copied python executable + try: + mach_o_change(py_executable, + os.path.join(prefix, 'Python'), + '@executable_path/../.Python') + except: + e = sys.exc_info()[1] + logger.warn("Could not call mach_o_change: %s. " + "Trying to call install_name_tool instead." % e) + try: + call_subprocess( + ["install_name_tool", "-change", + os.path.join(prefix, 'Python'), + '@executable_path/../.Python', + py_executable]) + except: + logger.fatal("Could not call install_name_tool -- you must " + "have Apple's development tools installed") + raise + + if not is_win: + # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist + py_exe_version_major = 'python%s' % sys.version_info[0] + py_exe_version_major_minor = 'python%s.%s' % ( + sys.version_info[0], sys.version_info[1]) + py_exe_no_version = 'python' + required_symlinks = [ py_exe_no_version, py_exe_version_major, + py_exe_version_major_minor ] + + py_executable_base = os.path.basename(py_executable) + + if py_executable_base in required_symlinks: + # Don't try to symlink to yourself. + required_symlinks.remove(py_executable_base) + + for pth in required_symlinks: + full_pth = join(bin_dir, pth) + if os.path.exists(full_pth): + os.unlink(full_pth) + if symlink: + os.symlink(py_executable_base, full_pth) + else: + copyfile(py_executable, full_pth, symlink) + + if is_win and ' ' in py_executable: + # There's a bug with subprocess on Windows when using a first + # argument that has a space in it. Instead we have to quote + # the value: + py_executable = '"%s"' % py_executable + # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks + cmd = [py_executable, '-c', 'import sys;out=sys.stdout;' + 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))'] + logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) + try: + proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE) + proc_stdout, proc_stderr = proc.communicate() + except OSError: + e = sys.exc_info()[1] + if e.errno == errno.EACCES: + logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) + sys.exit(100) + else: + raise e + + proc_stdout = proc_stdout.strip().decode("utf-8") + proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) + norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) + if hasattr(norm_home_dir, 'decode'): + norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) + if proc_stdout != norm_home_dir: + logger.fatal( + 'ERROR: The executable %s is not functioning' % py_executable) + logger.fatal( + 'ERROR: It thinks sys.prefix is %r (should be %r)' + % (proc_stdout, norm_home_dir)) + logger.fatal( + 'ERROR: virtualenv is not compatible with this system or executable') + if is_win: + logger.fatal( + 'Note: some Windows users have reported this error when they ' + 'installed Python for "Only this user" or have multiple ' + 'versions of Python installed. Copying the appropriate ' + 'PythonXX.dll to the virtualenv Scripts/ directory may fix ' + 'this problem.') + sys.exit(100) + else: + logger.info('Got sys.prefix result: %r' % proc_stdout) + + pydistutils = os.path.expanduser('~/.pydistutils.cfg') + if os.path.exists(pydistutils): + logger.notify('Please make sure you remove any previous custom paths from ' + 'your %s file.' % pydistutils) + ## FIXME: really this should be calculated earlier + + fix_local_scheme(home_dir, symlink) + + if site_packages: + if os.path.exists(site_packages_filename): + logger.info('Deleting %s' % site_packages_filename) + os.unlink(site_packages_filename) + + return py_executable + + +def install_activate(home_dir, bin_dir, prompt=None): + home_dir = os.path.abspath(home_dir) + if is_win or is_jython and os._name == 'nt': + files = { + 'activate.bat': ACTIVATE_BAT, + 'deactivate.bat': DEACTIVATE_BAT, + 'activate.ps1': ACTIVATE_PS, + } + + # MSYS needs paths of the form /c/path/to/file + drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/')) + home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) + + # Run-time conditional enables (basic) Cygwin compatibility + home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" % + (home_dir, home_dir_msys)) + files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh) + + else: + files = {'activate': ACTIVATE_SH} + + # suppling activate.fish in addition to, not instead of, the + # bash script support. + files['activate.fish'] = ACTIVATE_FISH + + # same for csh/tcsh support... + files['activate.csh'] = ACTIVATE_CSH + + files['activate_this.py'] = ACTIVATE_THIS + if hasattr(home_dir, 'decode'): + home_dir = home_dir.decode(sys.getfilesystemencoding()) + vname = os.path.basename(home_dir) + for name, content in files.items(): + content = content.replace('__VIRTUAL_PROMPT__', prompt or '') + content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) + content = content.replace('__VIRTUAL_ENV__', home_dir) + content = content.replace('__VIRTUAL_NAME__', vname) + content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) + writefile(os.path.join(bin_dir, name), content) + +def install_distutils(home_dir): + distutils_path = change_prefix(distutils.__path__[0], home_dir) + mkdir(distutils_path) + ## FIXME: maybe this prefix setting should only be put in place if + ## there's a local distutils.cfg with a prefix setting? + home_dir = os.path.abspath(home_dir) + ## FIXME: this is breaking things, removing for now: + #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir + writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) + writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) + +def fix_local_scheme(home_dir, symlink=True): + """ + Platforms that use the "posix_local" install scheme (like Ubuntu with + Python 2.7) need to be given an additional "local" location, sigh. + """ + try: + import sysconfig + except ImportError: + pass + else: + if sysconfig._get_default_scheme() == 'posix_local': + local_path = os.path.join(home_dir, 'local') + if not os.path.exists(local_path): + os.mkdir(local_path) + for subdir_name in os.listdir(home_dir): + if subdir_name == 'local': + continue + copyfile(os.path.abspath(os.path.join(home_dir, subdir_name)), \ + os.path.join(local_path, subdir_name), symlink) + +def fix_lib64(lib_dir, symlink=True): + """ + Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y + instead of lib/pythonX.Y. If this is such a platform we'll just create a + symlink so lib64 points to lib + """ + if [p for p in distutils.sysconfig.get_config_vars().values() + if isinstance(p, basestring) and 'lib64' in p]: + # PyPy's library path scheme is not affected by this. + # Return early or we will die on the following assert. + if is_pypy: + logger.debug('PyPy detected, skipping lib64 symlinking') + return + + logger.debug('This system uses lib64; symlinking lib64 to lib') + + assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( + "Unexpected python lib dir: %r" % lib_dir) + lib_parent = os.path.dirname(lib_dir) + top_level = os.path.dirname(lib_parent) + lib_dir = os.path.join(top_level, 'lib') + lib64_link = os.path.join(top_level, 'lib64') + assert os.path.basename(lib_parent) == 'lib', ( + "Unexpected parent dir: %r" % lib_parent) + if os.path.lexists(lib64_link): + return + cp_or_ln = (os.symlink if symlink else copyfile) + cp_or_ln('lib', lib64_link) + +def resolve_interpreter(exe): + """ + If the executable given isn't an absolute path, search $PATH for the interpreter + """ + # If the "executable" is a version number, get the installed executable for + # that version + python_versions = get_installed_pythons() + if exe in python_versions: + exe = python_versions[exe] + + if os.path.abspath(exe) != exe: + paths = os.environ.get('PATH', '').split(os.pathsep) + for path in paths: + if os.path.exists(os.path.join(path, exe)): + exe = os.path.join(path, exe) + break + if not os.path.exists(exe): + logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) + raise SystemExit(3) + if not is_executable(exe): + logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) + raise SystemExit(3) + return exe + +def is_executable(exe): + """Checks a file is executable""" + return os.access(exe, os.X_OK) + +############################################################ +## Relocating the environment: + +def make_environment_relocatable(home_dir): + """ + Makes the already-existing environment use relative paths, and takes out + the #!-based environment selection in scripts. + """ + home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) + activate_this = os.path.join(bin_dir, 'activate_this.py') + if not os.path.exists(activate_this): + logger.fatal( + 'The environment doesn\'t have a file %s -- please re-run virtualenv ' + 'on this environment to update it' % activate_this) + fixup_scripts(home_dir, bin_dir) + fixup_pth_and_egg_link(home_dir) + ## FIXME: need to fix up distutils.cfg + +OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], + 'activate', 'activate.bat', 'activate_this.py', + 'activate.fish', 'activate.csh'] + +def fixup_scripts(home_dir, bin_dir): + if is_win: + new_shebang_args = ( + '%s /c' % os.path.normcase(os.environ.get('COMSPEC', 'cmd.exe')), + '', '.exe') + else: + new_shebang_args = ('/usr/bin/env', sys.version[:3], '') + + # This is what we expect at the top of scripts: + shebang = '#!%s' % os.path.normcase(os.path.join( + os.path.abspath(bin_dir), 'python%s' % new_shebang_args[2])) + # This is what we'll put: + new_shebang = '#!%s python%s%s' % new_shebang_args + + for filename in os.listdir(bin_dir): + filename = os.path.join(bin_dir, filename) + if not os.path.isfile(filename): + # ignore subdirs, e.g. .svn ones. + continue + f = open(filename, 'rb') + try: + try: + lines = f.read().decode('utf-8').splitlines() + except UnicodeDecodeError: + # This is probably a binary program instead + # of a script, so just ignore it. + continue + finally: + f.close() + if not lines: + logger.warn('Script %s is an empty file' % filename) + continue + + old_shebang = lines[0].strip() + old_shebang = old_shebang[0:2] + os.path.normcase(old_shebang[2:]) + + if not old_shebang.startswith(shebang): + if os.path.basename(filename) in OK_ABS_SCRIPTS: + logger.debug('Cannot make script %s relative' % filename) + elif lines[0].strip() == new_shebang: + logger.info('Script %s has already been made relative' % filename) + else: + logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' + % (filename, shebang)) + continue + logger.notify('Making script %s relative' % filename) + script = relative_script([new_shebang] + lines[1:]) + f = open(filename, 'wb') + f.write('\n'.join(script).encode('utf-8')) + f.close() + +def relative_script(lines): + "Return a script that'll work in a relocatable environment." + activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); exec(compile(open(activate_this).read(), activate_this, 'exec'), dict(__file__=activate_this)); del os, activate_this" + # Find the last future statement in the script. If we insert the activation + # line before a future statement, Python will raise a SyntaxError. + activate_at = None + for idx, line in reversed(list(enumerate(lines))): + if line.split()[:3] == ['from', '__future__', 'import']: + activate_at = idx + 1 + break + if activate_at is None: + # Activate after the shebang. + activate_at = 1 + return lines[:activate_at] + ['', activate, ''] + lines[activate_at:] + +def fixup_pth_and_egg_link(home_dir, sys_path=None): + """Makes .pth and .egg-link files use relative paths""" + home_dir = os.path.normcase(os.path.abspath(home_dir)) + if sys_path is None: + sys_path = sys.path + for path in sys_path: + if not path: + path = '.' + if not os.path.isdir(path): + continue + path = os.path.normcase(os.path.abspath(path)) + if not path.startswith(home_dir): + logger.debug('Skipping system (non-environment) directory %s' % path) + continue + for filename in os.listdir(path): + filename = os.path.join(path, filename) + if filename.endswith('.pth'): + if not os.access(filename, os.W_OK): + logger.warn('Cannot write .pth file %s, skipping' % filename) + else: + fixup_pth_file(filename) + if filename.endswith('.egg-link'): + if not os.access(filename, os.W_OK): + logger.warn('Cannot write .egg-link file %s, skipping' % filename) + else: + fixup_egg_link(filename) + +def fixup_pth_file(filename): + lines = [] + prev_lines = [] + f = open(filename) + prev_lines = f.readlines() + f.close() + for line in prev_lines: + line = line.strip() + if (not line or line.startswith('#') or line.startswith('import ') + or os.path.abspath(line) != line): + lines.append(line) + else: + new_value = make_relative_path(filename, line) + if line != new_value: + logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) + lines.append(new_value) + if lines == prev_lines: + logger.info('No changes to .pth file %s' % filename) + return + logger.notify('Making paths in .pth file %s relative' % filename) + f = open(filename, 'w') + f.write('\n'.join(lines) + '\n') + f.close() + +def fixup_egg_link(filename): + f = open(filename) + link = f.readline().strip() + f.close() + if os.path.abspath(link) != link: + logger.debug('Link in %s already relative' % filename) + return + new_link = make_relative_path(filename, link) + logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) + f = open(filename, 'w') + f.write(new_link) + f.close() + +def make_relative_path(source, dest, dest_is_directory=True): + """ + Make a filename relative, where the filename is dest, and it is + being referred to from the filename source. + + >>> make_relative_path('/usr/share/something/a-file.pth', + ... '/usr/share/another-place/src/Directory') + '../another-place/src/Directory' + >>> make_relative_path('/usr/share/something/a-file.pth', + ... '/home/user/src/Directory') + '../../../home/user/src/Directory' + >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') + './' + """ + source = os.path.dirname(source) + if not dest_is_directory: + dest_filename = os.path.basename(dest) + dest = os.path.dirname(dest) + dest = os.path.normpath(os.path.abspath(dest)) + source = os.path.normpath(os.path.abspath(source)) + dest_parts = dest.strip(os.path.sep).split(os.path.sep) + source_parts = source.strip(os.path.sep).split(os.path.sep) + while dest_parts and source_parts and dest_parts[0] == source_parts[0]: + dest_parts.pop(0) + source_parts.pop(0) + full_parts = ['..']*len(source_parts) + dest_parts + if not dest_is_directory: + full_parts.append(dest_filename) + if not full_parts: + # Special case for the current directory (otherwise it'd be '') + return './' + return os.path.sep.join(full_parts) + + + +############################################################ +## Bootstrap script creation: + +def create_bootstrap_script(extra_text, python_version=''): + """ + Creates a bootstrap script, which is like this script but with + extend_parser, adjust_options, and after_install hooks. + + This returns a string that (written to disk of course) can be used + as a bootstrap script with your own customizations. The script + will be the standard virtualenv.py script, with your extra text + added (your extra text should be Python code). + + If you include these functions, they will be called: + + ``extend_parser(optparse_parser)``: + You can add or remove options from the parser here. + + ``adjust_options(options, args)``: + You can change options here, or change the args (if you accept + different kinds of arguments, be sure you modify ``args`` so it is + only ``[DEST_DIR]``). + + ``after_install(options, home_dir)``: + + After everything is installed, this function is called. This + is probably the function you are most likely to use. An + example would be:: + + def after_install(options, home_dir): + subprocess.call([join(home_dir, 'bin', 'easy_install'), + 'MyPackage']) + subprocess.call([join(home_dir, 'bin', 'my-package-script'), + 'setup', home_dir]) + + This example immediately installs a package, and runs a setup + script from that package. + + If you provide something like ``python_version='2.5'`` then the + script will start with ``#!/usr/bin/env python2.5`` instead of + ``#!/usr/bin/env python``. You can use this when the script must + be run with a particular Python version. + """ + filename = __file__ + if filename.endswith('.pyc'): + filename = filename[:-1] + f = codecs.open(filename, 'r', encoding='utf-8') + content = f.read() + f.close() + py_exe = 'python%s' % python_version + content = (('#!/usr/bin/env %s\n' % py_exe) + + '## WARNING: This file is generated\n' + + content) + return content.replace('##EXT' 'END##', extra_text) + +##EXTEND## + +def convert(s): + b = base64.b64decode(s.encode('ascii')) + return zlib.decompress(b).decode('utf-8') + +##file site.py +SITE_PY = convert(""" +eJzFPf1z2zaWv/OvwMqToZTIdOJ0e3tOnRsncVrvuYm3SWdz63q0lARZrCmSJUjL2pu7v/3eBwAC +JCXbm+6cphNLJPDw8PC+8PAeOhgMTopCZnOxyud1KoWScTlbiiKulkos8lJUy6Sc7xdxWW3g6ewm +vpZKVLlQGxVhqygInn7lJ3gqPi8TZVCAb3Fd5au4SmZxmm5EsiryspJzMa/LJLsWSZZUSZwm/4AW +eRaJp1+PQXCWCZh5mshS3MpSAVwl8oW42FTLPBPDusA5v4j+GL8cjYWalUlRQYNS4wwUWcZVkEk5 +BzShZa2AlEkl91UhZ8kimdmG67xO56JI45kUf/87T42ahmGg8pVcL2UpRQbIAEwJsArEA74mpZjl +cxkJ8UbOYhyAnzfEChjaGNdMIRmzXKR5dg1zyuRMKhWXGzGc1hUBIpTFPAecEsCgStI0WOfljRrB +ktJ6rOGRiJk9/Mkwe8A8cfwu5wCOH7Pg5yy5GzNs4B4EVy2ZbUq5SO5EjGDhp7yTs4l+NkwWYp4s +FkCDrBphk4ARUCJNpgcFLcd3eoVeHxBWlitjGEMiytyYX1KPKDirRJwqYNu6QBopwvydnCZxBtTI +bmE4gAgkDfrGmSeqsuPQ7EQOAEpcxwqkZKXEcBUnGTDrj/GM0P5rks3ztRoRBWC1lPi1VpU7/2EP +AaC1Q4BxgItlVrPO0uRGppsRIPAZsC+lqtMKBWKelHJW5WUiFQEA1DZC3gHSYxGXUpOQOdPI7Zjo +TzRJMlxYFDAUeHyJJFkk13VJEiYWCXAucMX7jz+Jd6dvzk4+aB4zwFhmr1eAM0ChhXZwggHEQa3K +gzQHgY6Cc/wj4vkchewaxwe8mgYH9650MIS5F1G7j7PgQHa9uHoYmGMFyoTGCqjff0OXsVoCff7n +nvUOgpNtVKGJ87f1MgeZzOKVFMuY+Qs5I/hOw3kdFdXyFXCDQjgVkErh4iCCCcIDkrg0G+aZFAWw +WJpkchQAhabU1l9FYIUPebZPa93iBIBQBhm8dJ6NaMRMwkS7sF6hvjCNNzQz3SSw67zKS1IcwP/Z +jHRRGmc3hKMihuJvU3mdZBkihLwQhHshDaxuEuDEeSTOqRXpBdNIhKy9uCWKRA28hEwHPCnv4lWR +yjGLL+rW3WqEBpOVMGudMsdBy4rUK61aM9Ve3juMvrS4jtCslqUE4PXUE7pFno/FFHQ2YVPEKxav +ap0T5wQ98kSdkCeoJfTF70DRE6XqlbQvkVdAsxBDBYs8TfM1kOwoCITYw0bGKPvMCW/hHfwLcPHf +VFazZRA4I1nAGhQivw0UAgGTIDPN1RoJj9s0K7eVTJKxpsjLuSxpqIcR+4ARf2BjnGvwIa+0UePp +4irnq6RClTTVJjNhi5eFFevHVzxvmAZYbkU0M00bOq1wemmxjKfSuCRTuUBJ0Iv0yi47jBn0jEm2 +uBIrtjLwDsgiE7Yg/YoFlc6ikuQEAAwWvjhLijqlRgoZTMQw0Kog+KsYTXqunSVgbzbLASokNt8z +sD+A2z9AjNbLBOgzAwigYVBLwfJNk6pEB6HRR4Fv9E1/Hh849WyhbRMPuYiTVFv5OAvO6OFpWZL4 +zmSBvcaaGApmmFXo2l1nQEcU88FgEATGHdoo8zVXQVVujoAVhBlnMpnWCRq+yQRNvf6hAh5FOAN7 +3Ww7Cw80hOn0AajkdFmU+Qpf27l9AmUCY2GPYE9ckJaR7CB7nPgKyeeq9MI0RdvtsLNAPRRc/HT6 +/uzL6SdxLC4blTZu67MrGPM0i4GtySIAU7WGbXQZtETFl6DuE+/BvBNTgD2j3iS+Mq5q4F1A/XNZ +02uYxsx7GZx+OHlzfjr5+dPpT5NPZ59PAUGwMzLYoymjeazBYVQRCAdw5VxF2r4GnR704M3JJ/sg +mCRq8u03wG7wZHgtK2DicggzHotwFd8pYNBwTE1HiGOnAVjwcDQSr8Xh06cvDwlasSk2AAzMrtMU +H060RZ8k2SIPR9T4V3bpj1lJaf/t8uibK3F8LMJf49s4DMCHapoyS/xI4vR5U0joWsGfYa5GQTCX +CxC9G4kCOnxKfvGIO8CSQMtc2+lf8yQz75kr3SFIfwypB+AwmczSWClsPJmEQATq0POBDhE71yh1 +Q+hYbNyuI40KfkoJC5thlzH+04NiPKV+iAaj6HYxjUBcV7NYSW5F04d+kwnqrMlkqAcEYSaJAYeL +1VAoTBPUWWUCfi1xHuqwqcpT/InwUQuQAOLWCrUkLpLeOkW3cVpLNXQmBUQcDltkREWbKOJHcFGG +YImbpRuN2tQ0PAPNgHxpDlq0bFEOP3vg74C6Mps43Ojx3otphpj+mXcahAO4nCGqe6VaUFg7iovT +C/Hy+eE+ujOw55xb6njN0UInWS3twwWslpEHRph7GXlx6bJAPYtPj3bDXEV2ZbqssNBLXMpVfivn +gC0ysLPK4id6AztzmMcshlUEvU7+AKtQ4zfGuA/l2YO0oO8A1FsRFLP+Zun3OBggMwWKiDfWRGq9 +62dTWJT5bYLOxnSjX4KtBGWJFtM4NoGzcB6ToUkEDQFecIaUWssQ1GFZs8NKeCNItBfzRrFGBO4c +NfUVfb3J8nU24Z3wMSrd4ciyLgqWZl5s0CzBnngPVgiQzGFj1xCNoYDLL1C29gF5mD5MFyhLewsA +BIZe0XbNgWW2ejRF3jXisAhj9EqQ8JYS/YVbMwRttQwxHEj0NrIPjJZASDA5q+CsatBMhrJmmsHA +Dkl8rjuPeAvqA2hRMQKzOdTQuJGh3+URKGdx7iolpx9a5C9fvjDbqCXFVxCxKU4aXYgFGcuo2IBh +TUAnGI+MozXEBmtwbgFMrTRriv1PIi/YG4P1vNCyDX4A7O6qqjg6OFiv15GOLuTl9YFaHPzxT99+ ++6fnrBPnc+IfmI4jLTrUFh3QO/Roo++MBXptVq7Fj0nmcyPBGkryysgVRfy+r5N5Lo72R1Z/Ihc3 +Zhr/Na4MKJCJGZSpDLQdNBg9UftPopdqIJ6QdbZthyP2S7RJtVbMt7rQo8rBEwC/ZZbXaKobTlDi +GVg32KHP5bS+Du3gno00P2CqKKdDywP7L64QA58zDF8ZUzxBLUFsgRbfIf1PzDYxeUdaQyB50UR1 +ds+bfi1miDt/uLxbX9MRGjPDRCF3oET4TR4sgLZxV3Lwo11btHuOa2s+niEwlj4wzKsdyyEKDuGC +azF2pc7havR4QZrWrJpBwbiqERQ0OIlTprYGRzYyRJDo3ZjNPi+sbgF0akUOTXzArAK0cMfpWLs2 +KzieEPLAsXhBTyS4yEedd895aes0pYBOi0c9qjBgb6HRTufAl0MDYCwG5c8Dbmm2KR9bi8Jr0AMs +5xgQMtiiw0z4xvUBB3uDHnbqWP1tvZnGfSBwkYYci3oQdEL5mEcoFUhTMfR7bmNxS9zuYDstDjGV +WSYSabVFuNrKo1eodhqmRZKh7nUWKZqlOXjFVisSIzXvfWeB9kH4uM+YaQnUZGjI4TQ6Jm/PE8BQ +t8Pw2XWNgQY3DoMYrRJF1g3JtIR/wK2g+AYFo4CWBM2CeaiU+RP7HWTOzld/2cIeltDIEG7TbW5I +x2JoOOb9nkAy6mgMSEEGJOwKI7mOrA5S4DBngTzhhtdyq3QTjEiBnDkWhNQM4E4vvQ0OPonwBIQk +FCHfVUoW4pkYwPK1RfVhuvt35VIThBg6DchV0NGLYzey4UQ1jltRDp+h/fgGnZUUOXDwFFweN9Dv +srlhWht0AWfdV9wWKdDIFIcZjFxUrwxh3GDyH46dFg2xzCCGobyBvCMdM9IosMutQcOCGzDemrfH +0o/diAX2HYa5OpSrO9j/hWWiZrkKKWbSjl24H80VXdpYbM+T6QD+eAswGF15kGSq4xcYZfknBgk9 +6GEfdG+yGBaZx+U6yUJSYJp+x/7SdPCwpPSM3MEn2k4dwEQx4nnwvgQBoaPPAxAn1ASwK5eh0m5/ +F+zOKQ4sXO4+8Nzmy6OXV13ijrdFeOynf6lO76oyVrhaKS8aCwWuVteAo9KFycXZRh9e6sNt3CaU +uYJdpPj46YtAQnBcdx1vHjf1huERm3vn5H0M6qDX7iVXa3bELoAIakVklIPw8Rz5cGQfO7kdE3sE +kEcxzI5FMZA0n/wzcHYtFIyxP99kGEdrqwz8wOtvv5n0REZdJL/9ZnDPKC1i9In9sOUJ2pE5qWDX +bEsZp+RqOH0oqJg1rGPbFCPW57T90zx21eNzarRs7Lu/BX4MFAypS/ARno8bsnWnih/fndoKT9up +HcA6u1Xz2aNFgL19Pv0VdshKB9Vu4ySlcwWY/P4+Klezued4Rb/28CDtVDAOCfr2X+ryOXBDyNGE +UXc62hk7MQHnnl2w+RSx6qKyp3MImiMwLy/APf7sQtUWzDDucz5eOOxRTd6M+5yJr1Gr+PldNJAF +5tFg0Ef2rez4/zHL5/+aST5wKubk+ne0ho8E9HvNhI0HQ9PGw4fVv+yu3TXAHmCetridO9zC7tB8 +Vrkwzh2rJCWeou56KtaUrkCxVTwpAihz9vt64OAy6kPvt3VZ8tE1qcBClvt4HDsWmKllPL9eE7Mn +Dj7ICjGxzWYUq3byevI+NRLq6LOdSdjsG/rlbJmbmJXMbpMS+oLCHYY/fPzxNOw3IRjHhU4PtyIP +9xsQ7iOYNtTECR/Thyn0mC7/vFS1ty4+QU1GgIkIa7L12gc/EGziCP1rcE9EyDuw5WN23KHPlnJ2 +M5GUOoBsil2doPhbfI2Y2IwCP/9LxQtKYoOZzNIaacWON2YfLupsRucjlQT/SqcKY+oQJQRw+G+R +xtdiSJ3nGHrS3EjRqdu41N5nUeaYnCrqZH5wncyF/K2OU9zWy8UCcMHDK/0q4uEpAiXecU4DJy0q +OavLpNoACWKV67M/Sn9wGk43PNGhhyQf8zABMSHiSHzCaeN7JtzckMsEB/wTD5wk7ruxg5OsENFz +eJ/lExx1Qjm+Y0aqey5Pj4P2CDkAGABQmP9gpCN3/htJr9wDRlpzl6ioJT1SupGGnJwxhDIcYaSD +f9NPnxFd3tqC5fV2LK93Y3ndxvK6F8trH8vr3Vi6IoELa4NWRhL6AlftY43efBs35sTDnMazJbfD +3E/M8QSIojAbbCNTnALtRbb4fI+AkNp2DpzpYZM/k3BSaZlzCFyDRO7HQyy9mTfJ605nysbRnXkq +xp3dlkPk9z2IIkoVm1J3lrd5XMWRJxfXaT4FsbXojhsAY9FOJ+JYaXY7mXJ0t2WpBhf/9fmHjx+w +OYIamPQG6oaLiIYFpzJ8GpfXqitNzeavAHakln4iDnXTAPceGFnjUfb4n3eU4YGMI9aUoZCLAjwA +yuqyzdzcpzBsPddJUvo5MzkfNh2LQVYNmkltIdLJxcW7k88nAwr5Df534AqMoa0vHS4+poVt0PXf +3OaW4tgHhFrHthrj587Jo3XDEffbWAO248O3Hhw+xGD3hgn8Wf5LKQVLAoSKdPD3MYR68B7oq7YJ +HfoYRuwk/7kna+ys2HeO7DkuiiP6fccO7QH8w07cY0yAANqFGpqdQbOZail9a153UNQB+kBf76u3 +YO2tV3sn41PUTqLHAXQoa5ttd/+8cxo2ekpWb06/P/twfvbm4uTzD44LiK7cx08Hh+L0xy+C8kPQ +gLFPFGNqRIWZSGBY3EInMc/hvxojP/O64iAx9Hp3fq5PalZY6oK5z2hzInjOaUwWGgfNOAptH+r8 +I8Qo1Rskp6aI0nWo5gj3SyuuZ1G5zo+mUqUpOqu13nrpWjFTU0bn2hFIHzR2ScEgOMUMXlEWe2V2 +hSWfAOo6qx6ktI22iSEpBQU76QLO+Zc5XfECpdQZnjSdtaK/DF1cw6tIFWkCO7lXoZUl3Q3TYxrG +0Q/tATfj1acBne4wsm7Is96KBVqtVyHPTfcfNYz2Ww0YNgz2DuadSUoPoQxsTG4TITbik5xQ3sFX +u/R6DRQsGB70VbiIhukSmH0Mm2uxTGADATy5BOuL+wSA0FoJ/0DgyIkOyByzM8K3q/n+X0JNEL/1 +L7/0NK/KdP9vooBdkOBUorCHmG7jd7DxiWQkTj++H4WMHKXmir/UWB4ADgkFQB1pp/wlPkGfDJVM +Fzq/xNcH+EL7CfS61b2URam797vGIUrAEzUkr+GJMvQLMd3Lwh7jVEYt0Fj5YDHDCkI3DcF89sSn +pUxTne9+9u78FHxHLMZACeJzt1MYjuMleISuk++4wrEFCg/Y4XWJbFyiC0tJFvPIa9YbtEaRo95e +XoZdJwoMd3t1osBlnCgX7SFOm2GZcoIIWRnWwiwrs3arDVLYbUMUR5lhlphclJTA6vME8DI9jXlL +BHslLPUwEXg+RU6yymQspskM9CioXFCoYxASJC7WMxLn5RnHwPNSmTIoeFhsyuR6WeHpBnSOqAQD +m/948uX87AOVJRy+bLzuHuYc005gzEkkx5giiNEO+OKm/SFXTSZ9PKtfIQzUPvCn/YqzU455gE4/ +Dizin/YrrkM7dnaCPANQUHXRFg/cADjd+uSmkQXG1e6D8eOmADaY+WAoFollLzrRw51flxNty5Yp +obiPefmIA5xFYVPSdGc3Ja390XNcFHjONR/2N4K3fbJlPlPoetN5sy35zf10pBBLYgGjbmt/DJMd +1mmqp+Mw2zZuoW2ttrG/ZE6s1Gk3y1CUgYhDt/PIZbJ+JaybMwd6adQdYOI7ja6RxF5VPvglG2gP +w8PEEruzTzEdqYyFjABGMqSu/anBh0KLAAqEsn+HjuSOR08PvTk61uD+OWrdBbbxB1CEOheXajzy +EjgRvvzGjiO/IrRQjx6J0PFUMpnlNk8MP+slepUv/Dn2ygAFMVHsyji7lkOGNTYwn/nE3hKCJW3r +kfoyueozLOIMnNO7LRzelYv+gxODWosROu1u5KatjnzyYIPeUpCdBPPBl/EadH9RV0NeyS3n0L21 +dNuh3g8Rsw+hqT59H4YYjvkt3LI+DeBeamhY6OH9tuUUltfGOLLWPraqmkL7QnuwsxK2ZpWiYxmn +ONH4otYLaAzucWPyB/apThSyv3vqxJyYkAXKg7sgvbkNdINWOGHA5UpcOZpQOnxTTaPfzeWtTMFo +gJEdYrXDr7baYRTZcEpvHthXY3exudj040ZvGsyOTDkGIkCFGL2Bnl0INTjgCv+idyJxdkPO8du/ +no3F2w8/wb9v5EewoFjzOBZ/g9HF27yEbSUX7dJtCljAUfF+Ma8VFkYSNDqh4Isn0Fu78MiLpyG6 +ssQvKbEKUmAybbni204ARZ4gFbI37oGpl4DfpqCr5YQaB7FvLQb6JdJge40L1oUc6JbRslqlaCac +4EiziJeD87O3px8+nUbVHTK2+Tlwgid+HhZORx8Nl3gMNhb2yazGJ1eOv/yDTIsed1nvNU29DO41 +RQjbkcLuL/kmjdjuKeISAwai2MzzWYQtgdO5RK9ag/88craV99p3z7girOFIH541Tjw+BmqIX9r6 +ZwANqY+eE/UkhOIp1orx42jQb4HHgiLa8OfpzXruBsR10Q9NsI1pM+uh392qwCXTWcOznER4Hdtl +MHWgaRKr1XTm1gd+zIS+CAWUGx1vyEVcp5WQGWylaG9PN1KAgndL+lhCmFXYilGdG0Vn0nW8UU7u +UazEAEcdUFE9nsNQoBC23j/GN2wGsNZQ1FwCDdAJUdo25U5XVc+WLMG8EyLq9eQbrJPspZvGoynM +g/LGeNb4rzBP9BYZo2tZ6fnzg+Ho8kWT4EDB6JlX0DsrwNi5bLIHGrN4+vTpQPzH/U4PoxKleX4D +3hjA7nVWzun1FoOtJ2dXq+vQmzcR8ONsKS/hwRUFze3zOqOI5I6utCDS/jUwQlyb0DKjad8yxxyr +K/l8mVvwOZU2GD9nCV13hBElicpW3xqF0SYjTcSSoBjCWM2SJOToBKzHJq+xFg+ji5pf5B1wfIJg +xvgWD8Z4h71Ex5LyZi33WHSOxYAADyiljEejYmaqRgM8JxcbjebkLEuqpozkuXtmqq8AqOwtRpqv +RLxGyTDzaBHDKev0WLVxrPOdLOptVPLZpRtnbM2SX9+HO7A2SFq+WBhM4aFZpFkuy5kxp7hiySyp +HDCmHcLhznR5E1mfKOhBaQDqnazC3Eq0ffsHuy4uph/p+HjfjKSzhip7IRbHhOKslVcYRc34FH2y +hLR8a76MYJQPFM3WnoA3lviDjqViDYF3b4dbzlhn+j4OTttoLukAOHQHlFWQlh09HeFcPGbhM9Nu +uUUDP7QzJ9xuk7Kq43Sir32YoJ82sefpGk9bBrezwNN6K+Db5+D47uuMfXAcTHIN0hMzbk1FxrFY +6MhE5FaW+UVYRY5e3iH7SuBTIGXmE1MPbWJHl5ZdbaGpTtV0VDyCemaKl7Y45KZqplNw4mI+pvQm +U+6wxXn2M0fp6grxWgxfjsVha+czKzZ4kxMg+2Qe+q4YdYOpOMEAM8f2vRji9bEYvhiLP+6AHm0Z +4OjQHaG9j21B2Ark5dWjyZgmUyJb2JfCfn9fncMImp5xHF21yd8l03dEpX9vUYkrBHWi8ot2onJr +7K371s7HRzJcgeJYJHK+/0QhCTXSjW7ezuCEHxbQ79kcLV073lTUUOHcFDYj60YPOhrRuM12EFOU +rtUX1++irmHDae8cMGkyrVRFe8scpjFq9FpEBQCTvqM0/IZ3u8B7TQrXP9t6xKqLACzYngiCrvTk +A7OmYSOo9zqCj9IA9zCKCPEwtVEUrmQ9QkRCugeHmOhZ6xDb4fjfnXm4xGDbUWgHy2+/2YWnK5i9 +RR09C7q70sITWVte0Sy3+fQH5jxG6ev6VQLjQGlEB5xVc1UluZlHmL3Md9DkNot5hZdB0sk0msRU +um4Tb6X51i/0Yyh2QMlksBbgSdULPEi+pbstTxQlveEVNd8cvhibymAGpCfwMnr5TF8BSd3M5Qe+ +jz3Wezd4qfsdRv/mAEsqv7d91dnN0LSOW3dB+YOFFD0bRRNLh8Yw3V8H0qxZLPDOxIaY7FvbC0De +g7czBT/HXH6ag8MGG9KoD11XYzTSu021bRHg+03GNsl5UNdGkSLSu4Rtm/LcpTgfLQq6V78FwRAC +cv4y5jfoCtbFkQ2xGZuCJ59DN5sTP9VNb90Z2xM0ttVNuGv63H/X3HWLwM7cJDN05u7Xl7o00H23 +W9E+GnB4QxPiQSUSjcbvNyauHRjrHJr+CL3+IPndTjjTLWblPjAmYwfj/cSeGntj9lfxzP2OCWH7 +fCGzW07c62y0pt2xGW2Of4inwMkv+NzeMEAZTXPNgbxfohv2JpwjO5HX12oS4+2OE9pkUz5XZ/dk +tm3v6XI+GauN2W3hpUUAwnCTzrx1k+uBMUBX8i3TnA7l3E4jaGhKGnaykFUyZ5Ogt3YALuKIKfU3 +gXhOIx6kEgPdqi6LEnbDA30XMefp9KU2N0BNAG8VqxuDuukx1lfTkmKl5DBTgsxx2laSDxCBjXjH +NEwm9h3wyvPmmoVkbJlBZvVKlnHVXDHkZwQksOlqRqCic1xcJzzXSGWLS1zEEssbDlIYILPfn8HG +0ttU77hXYWS13cPZiXrokO9jrmxwjJHh4uTOXi/oXms1p6utXe/QNmu4zl6pBMtg7sojHaljZfxW +39/Fd8xyJB/9S4d/QN7dyks/C92qM/ZuLRrOM1chdC9swhsDyDj33cPY4YDujYutDbAd39cXllE6 +HuaWxpaK2ifvVTjNaKMmgoQJo/dEkPyigEdGkDz4D4wg6VszwdBofLQe6C0TuCfUxOrBvYKyYQTo +MwEi4QF26wJDYyqHbtJ9kavkbmAvlGZd6VTyGfOAHNm9m4xA8FWTys1Q9q6C2xVB8qWLHn9//vHN +yTnRYnJx8vY/T76npCw8LmnZqgeH2LJ8n6m976V/u+E2nUjTN3iDbc8NsVzDpCF03ndyEHog9Ner +9S1oW5G5r7d16NT9dDsB4run3YK6TWX3Qu74ZbrGxE2faeVpB/opJ9WaX05mgnlkTupYHJqTOPO+ +OTzRMtqJLW9bOCe9tatOtL+qbwHdEvce2SRrWgE8M0H+skcmpmLGBubZQWn/bz4oMxyrDc0NOiCF +M+nc5EiXODKoyv//iZSg7GLc27GjOLZ3c1M7Ph5S9tJ5PPudycgQxCv3G3Tn5wr7XKZbqBAErPD0 +PYWMiNF/+kDVph88UeJynwqL91HZXNlfuGbauf1rgkkGlb3vS3GCEh+zQuNFnbqJA7ZPpwM5fXQa +lS+cShbQfAdA50Y8FbA3+kusEKcbEcLGUbtkmBxLdNSX9TnIo910sDe0ei72t5WdumWXQrzY3nDe +quzUPQ65h7qnh6pNcZ9jgTFLc1s9qXhNkPk4U9AFX57zgWfoetsPX28vXxzZwwXkd3ztKBLKJhs4 +hv3Sycbceamk052YpRxTuh7u1ZyQsG5x5UBln2Db3qZTkrJl/2PyHBjSwHvfHzIzPbyr9wdtTC3r +HcGUxPCJGtG0nCIejbt9MupOt1FbXSBckPQAIB0VCLAQTEc3OgmiG87yHj7Xu8FpTdfxuidMoSMV +lCzmcwT3ML5fg1+7OxUSP6g7o2j6c4M2B+olB+Fm34FbjbxQyHaT0J56wwdbXACuye7v/+IB/btp +jLb74S6/2rZ62VsHyL4sZr5iZlCLROZxBEYG9OaQtDWWSxhBx2toGjq6DNXMDfkCHT/KpsXLtmmD +Qc7sRHsA1igE/wfVIOdx +""") + +##file activate.sh +ACTIVATE_SH = convert(""" +eJytVVFvokAQfudXTLEPtTlLeo9tvMSmJpq02hSvl7u2wRUG2QR2DSxSe7n/frOACEVNLlceRHa+ +nfl25pvZDswCnoDPQ4QoTRQsENIEPci4CsBMZBq7CAsuLOYqvmYKTTj3YxnBgiXBudGBjUzBZUJI +BXEqgCvweIyuCjeG4eF2F5x14bcB9KQiQQWrjSddI1/oQIx6SYYeoFjzWIoIhYI1izlbhJjkKO7D +M/QEmKfO9O7WeRo/zr4P7pyHwWxkwitcgwpQ5Ej96OX+PmiFwLeVjFUOrNYKaq1Nud3nR2n8nI2m +k9H0friPTGVsUdptaxGrTEfpNVFEskxpXtUkkCkl1UNF9cgLBkx48J4EXyALuBtAwNYIjF5kcmUU +abMKmMq1ULoiRbgsDEkTSsKSGFCJ6Z8vY/2xYiSacmtyAfCDdCNTVZoVF8vSTQOoEwSnOrngBkws +MYGMBMg8/bMBLSYKS7pYEXP0PqT+ZmBT0Xuy+Pplj5yn4aM9nk72JD8/Wi+Gr98sD9eWSMOwkapD +BbUv91XSvmyVkICt2tmXR4tWmrcUCsjWOpw87YidEC8i0gdTSOFhouJUNxR+4NYBG0MftoCTD9F7 +2rTtxG3oPwY1b2HncYwhrlmj6Wq924xtGDWqfdNxap+OYxplEurnMVo9RWks+rH8qKEtx7kZT5zJ +4H7oOFclrN6uFe+d+nW2aIUsSgs/42EIPuOhXq+jEo3S6tX6w2ilNkDnIpHCWdEQhFgwj9pkk7FN +l/y5eQvRSIQ5+TrL05lewxWpt/Lbhes5cJF3mLET1MGhcKCF+40tNWnUulxrpojwDo2sObdje3Bz +N3QeHqf3D7OjEXMVV8LN3ZlvuzoWHqiUcNKHtwNd0IbvPGKYYM31nPKCgkUILw3KL+Y8l7aO1ArS +Ad37nIU0fCj5NE5gQCuC5sOSu+UdI2NeXg/lFkQIlFpdWVaWZRfvqGiirC9o6liJ9FXGYrSY9mI1 +D/Ncozgn13vJvsznr7DnkJWXsyMH7e42ljdJ+aqNDF1bFnKWFLdj31xtaJYK6EXFgqmV/ymD/ROG ++n8O9H8f5vsGOWXsL1+1k3g= +""") + +##file activate.fish +ACTIVATE_FISH = convert(""" +eJydVW2P2jgQ/s6vmAZQoVpA9/WkqqJaTou0u6x2uZVOVWWZZEKsS+yc7UDpr+84bziQbauLxEvs +eXnsZ56ZIWwTYSAWKUJWGAs7hMJgBEdhEwiMKnSIsBNywUMrDtziPBYmCeBDrFUG7v8HmCTW5n8u +Fu7NJJim81Bl08EQTqqAkEupLOhCgrAQCY2hTU+DQVxIiqgkRNiEBphFEKy+kd1BaFvwFOUBuIxA +oy20BKtAKp3xFMo0QNtCK5mhtMEA6BmSpUELKo38TThwLfguRVNaiRgs0llnEoIR29zfstf18/bv +5T17Wm7vAiiN3ONCzfbfwC3DtWXXDqHfAGX0q6z/bO82j3ebh1VwnbrduwTQbvwcRtesAfMGor/W +L3fs6Xnz8LRlm9fV8/P61sM0LDNwCZjl9gSpCokJRzpryGQ5t8kNGFUt51QjOZGu0Mj35FlYlXEr +yC09EVOp4lEXfF84Lz1qbhBsgl59vDedXI3rTV03xipduSgt9kLytI3XmBp3aV6MPoMQGNUU62T6 +uQdeefTy1Hfj10zVHg2pq8fXDoHBiOv94csfXwN49xECqWREy7pwukKfvxdMY2j23vXDPuuxxeE+ +JOdCOhxCE3N44B1ZeSLuZh8Mmkr2wEPAmPfKWHA2uxIRjEopdbQYjDz3BWOf14/scfmwoki1eQvX +ExBdF60Mqh+Y/QcX4uiH4Amwzx79KOVFtbL63sXJbtcvy8/3q5rupmO5CnE91wBviQAhjUUegYpL +vVEbpLt2/W+PklRgq5Ku6mp+rpMhhCo/lXthQTxJ2ysO4Ka0ad97S7VT/n6YXus6fzk3fLnBZW5C +KDC6gSO62QDqgFqLCCtPmjegjnLeAdArtSE8VYGbAJ/aLb+vnQutFhk768E9uRbSxhCMzdgEveYw +IZ5ZqFKl6+kz7UR4U+buqQZXu9SIujrAfD7f0FXpozB4Q0gwp31H9mVTZGGC4b871/wm7lvyDLu1 +FUyvTj/yvD66k3UPTs08x1AQQaGziOl0S1qRkPG9COtBTSTWM9NzQ4R64B+Px/l3tDzCgxv5C6Ni +e+QaF9xFWrxx0V/G5uvYQOdiZzvYpQUVQSIsTr1TTghI33GnPbTA7/GCqcE3oE3GZurq4HeQXQD6 +32XS1ITj/qLjN72ob0hc5C9bzw8MhfmL +""") + +##file activate.csh +ACTIVATE_CSH = convert(""" +eJx9VG1P2zAQ/u5fcYQKNgTNPtN1WxlIQ4KCUEGaxuQ6yYVYSuzKdhqVX7+zk3bpy5YPUXL3PPfc +ne98DLNCWshliVDV1kGCUFvMoJGugMjq2qQIiVSxSJ1cCofD1BYRnOVGV0CfZ0N2DD91DalQSjsw +tQLpIJMGU1euvPe7QeJlkKzgWixlhnAt4aoUVsLnLBiy5NtbJWQ5THX1ZciYKKWwkOFaE04dUm6D +r/zh7pq/3D7Nnid3/HEy+wFHY/gEJydg0aFaQrBFgz1c5DG1IhTs+UZgsBC2GMFBlaeH+8dZXwcW +VPvCjXdlAvCfQsE7al0+07XjZvrSCUevR5dnkVeKlFYZmUztG4BdzL2u9KyLVabTU0bdfg7a0hgs +cSmUg6UwUiQl2iHrcbcVGNvPCiLOe7+cRwG13z9qRGgx2z6DHjfm/Op2yqeT+xvOLzs0PTKHDz2V +tkckFHoQfQRXoGJAj9el0FyJCmEMhzgMS4sB7KPOE2ExoLcSieYwDvR+cP8cg11gKkVJc2wRcm1g +QhYFlXiTaTfO2ki0fQoiFM4tLuO4aZrhOzqR4dIPcWx17hphMBY+Srwh7RTyN83XOWkcSPh1Pg/k +TXX/jbJTbMtUmcxZ+/bbqOsy82suFQg/BhdSOTRhMNBHlUarCpU7JzBhmkKmRejKOQzayQe6MWoa +n1wqWmuh6LZAaHxcdeqIlVLhIBJdO9/kbl0It2oEXQj+eGjJOuvOIR/YGRqvFhttUB2XTvLXYN2H +37CBdbW2W7j2r2+VsCn0doVWcFG1/4y1VwBjfwAyoZhD +""") + +##file activate.bat +ACTIVATE_BAT = convert(""" +eJx9UdEKgjAUfW6wfxjiIH+hEDKUFHSKLCMI7kNOEkIf9P9pTJ3OLJ/03HPPPed4Es9XS9qqwqgT +PbGKKOdXL4aAFS7A4gvAwgijuiKlqOpGlATS2NeMLE+TjJM9RkQ+SmqAXLrBo1LLIeLdiWlD6jZt +r7VNubWkndkXaxg5GO3UaOOKS6drO3luDDiO5my3iA0YAKGzPRV1ack8cOdhysI0CYzIPzjSiH5X +0QcvC8Lfaj0emsVKYF2rhL5L3fCkVjV76kShi59NHwDniAHzkgDgqBcwOgTMx+gDQQqXCw== +""") + +##file deactivate.bat +DEACTIVATE_BAT = convert(""" +eJxzSE3OyFfIT0vj4ipOLVEI8wwKCXX0iXf1C7Pl4spMU0hJTcvMS01RiPf3cYmHyQYE+fsGhCho +cCkAAUibEkTEVhWLMlUlLk6QGixStlyaeCyJDPHw9/Pw93VFsQguim4ZXAJoIUw5DhX47XUM8UCx +EchHtwsohN1bILUgw61c/Vy4AJYPYm4= +""") + +##file activate.ps1 +ACTIVATE_PS = convert(""" +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 +QastYw== +""") + +##file distutils-init.py +DISTUTILS_INIT = convert(""" +eJytV1uL4zYUfvevOE0ottuMW9q3gVDa3aUMXXbLMlDKMBiNrSTqOJKRlMxkf33PkXyRbGe7Dw2E +UXTu37lpxLFV2oIyifAncxmOL0xLIfcG+gv80x9VW6maw7o/CANSWWBwFtqeWMPlGY6qPjV8A0bB +C4eKSTgZ5LRgFeyErMEeOBhbN+Ipgeizhjtnhkn7DdyjuNLPoCS0l/ayQTG0djwZC08cLXozeMss +aG5EzQ0IScpnWtHSTXuxByV/QCmxE7y+eS0uxWeoheaVVfqSJHiU7Mhhi6gULbOHorshkrEnKxpT +0n3A8Y8SMpuwZx6aoix3ouFlmW8gHRSkeSJ2g7hU+kiHLDaQw3bmRDaTGfTnty7gPm0FHbIBg9U9 +oh1kZzAFLaue2R6htPCtAda2nGlDSUJ4PZBgCJBGVcwKTAMz/vJiLD+Oin5Z5QlvDPdulC6EsiyE +NFzb7McNTKJzbJqzphx92VKRFY1idenzmq3K0emRcbWBD0ryqc4NZGmKOOOX9Pz5x+/l27tP797c +f/z0d+4NruGNai8uAM0bfsYaw8itFk8ny41jsfpyO+BWlpqfhcG4yxLdi/0tQqoT4a8Vby382mt8 +p7XSo7aWGdPBc+b6utaBmCQ7rQKQoWtAuthQCiold2KfJIPTT8xwg9blPumc+YDZC/wYGdAyHpJk +vUbHbHWAp5No6pK/WhhLEWrFjUwtPEv1Agf8YmnsuXUQYkeZoHm8ogP16gt2uHoxcEMdf2C6pmbw +hUMsWGhanboh4IzzmsIpWs134jVPqD/c74bZHdY69UKKSn/+KfVhxLgUlToemayLMYQOqfEC61bh +cbhwaqoGUzIyZRFHPmau5juaWqwRn3mpWmoEA5nhzS5gog/5jbcFQqOZvmBasZtwYlG93k5GEiyw +buHhMWLjDarEGpMGB2LFs5nIJkhp/nUmZneFaRth++lieJtHepIvKgx6PJqIlD9X2j6pG1i9x3pZ +5bHuCPFiirGHeO7McvoXkz786GaKVzC9DSpnOxJdc4xm6NSVq7lNEnKdVlnpu9BNYoKX2Iq3wvgh +gGEUM66kK6j4NiyoneuPLSwaCWDxczgaolEWpiMyDVDb7dNuLAbriL8ig8mmeju31oNvQdpnvEPC +1vAXbWacGRVrGt/uXN/gU0CDDwgooKRrHfTBb1/s9lYZ8ZqOBU0yLvpuP6+K9hLFsvIjeNhBi0KL +MlOuWRn3FRwx5oHXjl0YImUx0+gLzjGchrgzca026ETmYJzPD+IpuKzNi8AFn048Thd63OdD86M6 +84zE8yQm0VqXdbbgvub2pKVnS76icBGdeTHHXTKspUmr4NYo/furFLKiMdQzFjHJNcdAnMhltBJK +0/IKX3DVFqvPJ2dLE7bDBkH0l/PJ29074+F0CsGYOxsb7U3myTUncYfXqnLLfa6sJybX4g+hmcjO +kMRBfA1JellfRRKJcyRpxdS4rIl6FdmQCWjo/o9Qz7yKffoP4JHjOvABcRn4CZIT2RH4jnxmfpVG +qgLaAvQBNfuO6X0/Ux02nb4FKx3vgP+XnkX0QW9pLy/NsXgdN24dD3LxO2Nwil7Zlc1dqtP3d7/h +kzp1/+7hGBuY4pk0XD/0Ao/oTe/XGrfyM773aB7iUhgkpy+dwAMalxMP0DrBcsVw/6p25+/hobP9 +GBknrWExDhLJ1bwt1NcCNblaFbMKCyvmX0PeRaQ= +""") + +##file distutils.cfg +DISTUTILS_CFG = convert(""" +eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH +xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg +9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= +""") + +##file activate_this.py +ACTIVATE_THIS = convert(""" +eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR +fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe +5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq +siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0 +y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd +FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar +XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS +PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj +YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t +s3az+sj7eA0jfgPfeoN1 +""") + +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe +FAT_MAGIC = 0xcafebabe +BIG_ENDIAN = '>' +LITTLE_ENDIAN = '<' +LC_LOAD_DYLIB = 0xc +maxint = majver == 3 and getattr(sys, 'maxsize') or getattr(sys, 'maxint') + + +class fileview(object): + """ + A proxy for file-like objects that exposes a given view of a file. + Modified from macholib. + """ + + def __init__(self, fileobj, start=0, size=maxint): + if isinstance(fileobj, fileview): + self._fileobj = fileobj._fileobj + else: + self._fileobj = fileobj + self._start = start + self._end = start + size + self._pos = 0 + + def __repr__(self): + return '' % ( + self._start, self._end, self._fileobj) + + def tell(self): + return self._pos + + def _checkwindow(self, seekto, op): + if not (self._start <= seekto <= self._end): + raise IOError("%s to offset %d is outside window [%d, %d]" % ( + op, seekto, self._start, self._end)) + + def seek(self, offset, whence=0): + seekto = offset + if whence == os.SEEK_SET: + seekto += self._start + elif whence == os.SEEK_CUR: + seekto += self._start + self._pos + elif whence == os.SEEK_END: + seekto += self._end + else: + raise IOError("Invalid whence argument to seek: %r" % (whence,)) + self._checkwindow(seekto, 'seek') + self._fileobj.seek(seekto) + self._pos = seekto - self._start + + def write(self, bytes): + here = self._start + self._pos + self._checkwindow(here, 'write') + self._checkwindow(here + len(bytes), 'write') + self._fileobj.seek(here, os.SEEK_SET) + self._fileobj.write(bytes) + self._pos += len(bytes) + + def read(self, size=maxint): + assert size >= 0 + here = self._start + self._pos + self._checkwindow(here, 'read') + size = min(size, self._end - here) + self._fileobj.seek(here, os.SEEK_SET) + bytes = self._fileobj.read(size) + self._pos += len(bytes) + return bytes + + +def read_data(file, endian, num=1): + """ + Read a given number of 32-bits unsigned integers from the given file + with the given endianness. + """ + res = struct.unpack(endian + 'L' * num, file.read(num * 4)) + if len(res) == 1: + return res[0] + return res + + +def mach_o_change(path, what, value): + """ + Replace a given name (what) in any LC_LOAD_DYLIB command found in + the given binary with a new name (value), provided it's shorter. + """ + + def do_macho(file, bits, endian): + # Read Mach-O header (the magic number is assumed read by the caller) + cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = read_data(file, endian, 6) + # 64-bits header has one more field. + if bits == 64: + read_data(file, endian) + # The header is followed by ncmds commands + for n in range(ncmds): + where = file.tell() + # Read command header + cmd, cmdsize = read_data(file, endian, 2) + if cmd == LC_LOAD_DYLIB: + # The first data field in LC_LOAD_DYLIB commands is the + # offset of the name, starting from the beginning of the + # command. + name_offset = read_data(file, endian) + file.seek(where + name_offset, os.SEEK_SET) + # Read the NUL terminated string + load = file.read(cmdsize - name_offset).decode() + load = load[:load.index('\0')] + # If the string is what is being replaced, overwrite it. + if load == what: + file.seek(where + name_offset, os.SEEK_SET) + file.write(value.encode() + '\0'.encode()) + # Seek to the next command + file.seek(where + cmdsize, os.SEEK_SET) + + def do_file(file, offset=0, size=maxint): + file = fileview(file, offset, size) + # Read magic number + magic = read_data(file, BIG_ENDIAN) + if magic == FAT_MAGIC: + # Fat binaries contain nfat_arch Mach-O binaries + nfat_arch = read_data(file, BIG_ENDIAN) + for n in range(nfat_arch): + # Read arch header + cputype, cpusubtype, offset, size, align = read_data(file, BIG_ENDIAN, 5) + do_file(file, offset, size) + elif magic == MH_MAGIC: + do_macho(file, 32, BIG_ENDIAN) + elif magic == MH_CIGAM: + do_macho(file, 32, LITTLE_ENDIAN) + elif magic == MH_MAGIC_64: + do_macho(file, 64, BIG_ENDIAN) + elif magic == MH_CIGAM_64: + do_macho(file, 64, LITTLE_ENDIAN) + + assert(len(what) >= len(value)) + do_file(open(path, 'r+b')) + + +if __name__ == '__main__': + main() + +## TODO: +## Copy python.exe.manifest +## Monkeypatch distutils.sysconfig diff --git a/src/build_utils/virtualenv/virtualenv_support/pip-1.5.6-py2.py3-none-any.whl b/src/build_utils/virtualenv/virtualenv_support/pip-1.5.6-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..097ab43430d4c1302b0be353a8c16407c370693b GIT binary patch literal 1002021 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