From d763e921418dab339e45406c6083b3540695abd6 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 24 Dec 2018 13:42:15 -0800 Subject: commands: add `spack resource` command to inspect downloadable files - currently just looks at patches - allows you to find out which package applied a patch to a spec - intended to work with tarballs and resources in the future. - add tab completion for `spack resource` and subcommands --- lib/spack/docs/packaging_guide.rst | 67 ++++++++++++++++++++++++++++ lib/spack/spack/cmd/resource.py | 84 ++++++++++++++++++++++++++++++++++++ lib/spack/spack/test/cmd/resource.py | 60 ++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 lib/spack/spack/cmd/resource.py create mode 100644 lib/spack/spack/test/cmd/resource.py (limited to 'lib') diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 647c38b6ec..023a250104 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -1422,6 +1422,73 @@ with all packages in Spack, a patched dependency library can coexist with other versions of that library. See the `section on depends_on `_ for more details. +.. _patch_inspecting_patches: + +^^^^^^^^^^^^^^^^^^^ +Inspecting patches +^^^^^^^^^^^^^^^^^^^ + +If you want to better understand the patches that Spack applies to your +packages, you can do that using ``spack spec``, ``spack find``, and other +query commands. Let's look at ``m4``. If you run ``spack spec m4``, you +can see the patches that would be applied to ``m4``:: + + $ spack spec m4 + Input spec + -------------------------------- + m4 + + Concretized + -------------------------------- + m4@1.4.18%clang@9.0.0-apple patches=3877ab548f88597ab2327a2230ee048d2d07ace1062efe81fc92e91b7f39cd00,c0a408fbffb7255fcc75e26bd8edab116fc81d216bfd18b473668b7739a4158e,fc9b61654a3ba1a8d6cd78ce087e7c96366c290bc8d2c299f09828d793b853c8 +sigsegv arch=darwin-highsierra-x86_64 + ^libsigsegv@2.11%clang@9.0.0-apple arch=darwin-highsierra-x86_64 + +You can also see patches that have been applied to installed packages +with ``spack find -v``:: + + $ spack find -v m4 + ==> 1 installed package + -- darwin-highsierra-x86_64 / clang@9.0.0-apple ----------------- + m4@1.4.18 patches=3877ab548f88597ab2327a2230ee048d2d07ace1062efe81fc92e91b7f39cd00,c0a408fbffb7255fcc75e26bd8edab116fc81d216bfd18b473668b7739a4158e,fc9b61654a3ba1a8d6cd78ce087e7c96366c290bc8d2c299f09828d793b853c8 +sigsegv + +.. _cmd-spack-resource: + +In both cases above, you can see that the patches' sha256 hashes are +stored on the spec as a variant. As mentioned above, this means that you +can have multiple, differently-patched versions of a package installed at +once. + +You can look up a patch by its sha256 hash (or a short version of it) +using the ``spack resource show`` command:: + + $ spack resource show 3877ab54 + 3877ab548f88597ab2327a2230ee048d2d07ace1062efe81fc92e91b7f39cd00 + path: /home/spackuser/src/spack/var/spack/repos/builtin/packages/m4/gnulib-pgi.patch + applies to: builtin.m4 + +``spack resource show`` looks up downloadable resources from package +files by hash and prints out information about them. Above, we see that +the ``3877ab54`` patch applies to the ``m4`` package. The output also +tells us where to find the patch. + +Things get more interesting if you want to know about dependency +patches. For example, when ``dealii`` is built with ``boost@1.68.0``, it +has to patch boost to work correctly. If you didn't know this, you might +wonder where the extra boost patches are coming from:: + + $ spack spec dealii ^boost@1.68.0 ^hdf5+fortran | grep '\^boost' + ^boost@1.68.0 + ^boost@1.68.0%clang@9.0.0-apple+atomic+chrono~clanglibcpp cxxstd=default +date_time~debug+exception+filesystem+graph~icu+iostreams+locale+log+math~mpi+multithreaded~numpy patches=2ab6c72d03dec6a4ae20220a9dfd5c8c572c5294252155b85c6874d97c323199,b37164268f34f7133cbc9a4066ae98fda08adf51e1172223f6a969909216870f ~pic+program_options~python+random+regex+serialization+shared+signals~singlethreaded+system~taggedlayout+test+thread+timer~versionedlayout+wave arch=darwin-highsierra-x86_64 + $ spack resource show b37164268 + b37164268f34f7133cbc9a4066ae98fda08adf51e1172223f6a969909216870f + path: /home/spackuser/src/spack/var/spack/repos/builtin/packages/dealii/boost_1.68.0.patch + applies to: builtin.boost + patched by: builtin.dealii + +Here you can see that the patch is applied to ``boost`` by ``dealii``, +and that it lives in ``dealii``'s directory in Spack's ``builtin`` +package repository. + .. _handling_rpaths: --------------- diff --git a/lib/spack/spack/cmd/resource.py b/lib/spack/spack/cmd/resource.py new file mode 100644 index 0000000000..36f6063c5b --- /dev/null +++ b/lib/spack/spack/cmd/resource.py @@ -0,0 +1,84 @@ +# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from __future__ import print_function +import os + +import llnl.util.tty as tty +import llnl.util.tty.color as color + +import spack.repo + + +description = "list downloadable resources (tarballs, repos, patches, etc.)" +section = "basic" +level = "long" + + +def setup_parser(subparser): + sp = subparser.add_subparsers( + metavar='SUBCOMMAND', dest='resource_command') + + list_parser = sp.add_parser('list', help=resource_list.__doc__) + list_parser.add_argument('--only-hashes', action='store_true', + help='only print sha256 hashes of resources') + + show_parser = sp.add_parser('show', help=resource_show.__doc__) + show_parser.add_argument('hash', action='store') + + +def _show_patch(sha256): + """Show a record from the patch index.""" + patches = spack.repo.path.patch_index.index + data = patches.get(sha256) + + if not data: + candidates = [k for k in patches if k.startswith(sha256)] + if not candidates: + tty.die('no such resource: %s' % sha256) + elif len(candidates) > 1: + tty.die('%s: ambiguous hash prefix. Options are:', + *candidates) + + sha256 = candidates[0] + data = patches.get(sha256) + + color.cprint('@c{%s}' % sha256) + for package, rec in data.items(): + owner = rec['owner'] + + if 'relative_path' in rec: + pkg_dir = spack.repo.get(owner).package_dir + path = os.path.join(pkg_dir, rec['relative_path']) + print(" path: %s" % path) + else: + print(" url: %s" % rec['url']) + + print(" applies to: %s" % package) + if owner != package: + print(" patched by: %s" % owner) + + +def resource_list(args): + """list all resources known to spack (currently just patches)""" + patches = spack.repo.path.patch_index.index + for sha256 in patches: + if args.only_hashes: + print(sha256) + else: + _show_patch(sha256) + + +def resource_show(args): + """show a resource, identified by its checksum""" + _show_patch(args.hash) + + +def resource(parser, args): + action = { + 'list': resource_list, + 'show': resource_show + } + action[args.resource_command](args) diff --git a/lib/spack/spack/test/cmd/resource.py b/lib/spack/spack/test/cmd/resource.py new file mode 100644 index 0000000000..dde61ed96d --- /dev/null +++ b/lib/spack/spack/test/cmd/resource.py @@ -0,0 +1,60 @@ +# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.main import SpackCommand + + +resource = SpackCommand('resource') + +#: these are hashes used in mock packages +mock_hashes = [ + 'abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234', + '1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd', + 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', + 'c45c1564f70def3fc1a6e22139f62cb21cd190cc3a7dbe6f4120fa59ce33dcb8', + '24eceabef5fe8f575ff4b438313dc3e7b30f6a2d1c78841fbbe3b9293a589277', + '689b8f9b32cb1d2f9271d29ea3fca2e1de5df665e121fca14e1364b711450deb', + 'ebe27f9930b99ebd8761ed2db3ea365142d0bafd78317efb4baadf62c7bf94d0', + '208fcfb50e5a965d5757d151b675ca4af4ce2dfd56401721b6168fae60ab798f', + 'bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c', + '7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', +] + + +def test_resource_list(mock_packages, capfd): + with capfd.disabled(): + out = resource('list') + + for h in mock_hashes: + assert h in out + + assert 'url:' in out + assert 'applies to:' in out + assert 'patched by:' in out + assert 'path:' in out + + assert 'repos/builtin.mock/packages/patch-a-dependency/libelf.patch' in out + assert 'applies to: builtin.mock.libelf' in out + assert 'patched by: builtin.mock.patch-a-dependency' in out + + +def test_resource_list_only_hashes(mock_packages, capfd): + with capfd.disabled(): + out = resource('list', '--only-hashes') + + for h in mock_hashes: + assert h in out + + +def test_resource_show(mock_packages, capfd): + with capfd.disabled(): + out = resource('show', 'c45c1564f70def3fc1a6e22139f62cb21cd190cc3a7dbe6f4120fa59ce33dcb8') + + assert out.startswith('c45c1564f70def3fc1a6e22139f62cb21cd190cc3a7dbe6f4120fa59ce33dcb8') + assert 'repos/builtin.mock/packages/patch-a-dependency/libelf.patch' in out + assert 'applies to: builtin.mock.libelf' in out + assert 'patched by: builtin.mock.patch-a-dependency' in out + + assert len(out.strip().split('\n')) == 4 -- cgit v1.2.3-60-g2f50