summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarmen Stoppels <harmenstoppels@gmail.com>2023-01-16 19:14:41 +0100
committerGitHub <noreply@github.com>2023-01-16 10:14:41 -0800
commit3489cc0a9b76837f0613d698815cb24f0eace07c (patch)
tree71c34b43136659c8ea87f4dd6ff57aa51b397a2b
parent9a25e21da85afd36dab208f7d228a0c9d52fc68f (diff)
downloadspack-3489cc0a9b76837f0613d698815cb24f0eace07c.tar.gz
spack-3489cc0a9b76837f0613d698815cb24f0eace07c.tar.bz2
spack-3489cc0a9b76837f0613d698815cb24f0eace07c.tar.xz
spack-3489cc0a9b76837f0613d698815cb24f0eace07c.zip
Refer to mirrors by name, path, or url (#34891)
With this change we get the invariant that `mirror.fetch_url` and `mirror.push_url` return valid URLs, even when the backing config file is actually using (relative) paths with potentially `$spack` and `$env` like variables. Secondly it avoids expanding mirror path / URLs too early, so if I say `spack mirror add name ./path`, it stays `./path` in my config. When it's retrieved through MirrorCollection() we exand it to say `file://<env dir>/path` if `./path` was set in an environment scope. Thirdly, the interface is simplified for the relevant buildcache commands, so it's more like `git push`: ``` spack buildcache create [mirror] [specs...] ``` `mirror` is either a mirror name, a path, or a URL. Resolving the relevant mirror goes as follows: - If it contains either / or \ it is used as an anonymous mirror with path or url. - Otherwise, it's interpreted as a named mirror, which must exist. This helps to guard against typos, e.g. typing `my-mirror` when there is no such named mirror now errors with: ``` $ spack -e . buildcache create my-mirror ==> Error: no mirror named "my-mirror". Did you mean ./my-mirror? ``` instead of creating a directory in the current working directory. I think this is reasonable, as the alternative (requiring that a local dir exists) feels a bit pendantic in the general case -- spack is happy to create the build cache dir when needed, saving a `mkdir`. The old (now deprecated) format will still be available in Spack 0.20, but is scheduled to be removed in 0.21: ``` spack buildcache create (--directory | --mirror-url | --mirror-name) [specs...] ``` This PR also touches `tmp_scope` in tests, because it didn't really work for me, since spack fixes the possible --scope values once and for all across tests, so tests failed when run out of order.
-rw-r--r--lib/spack/docs/binary_caches.rst110
-rw-r--r--lib/spack/spack/bootstrap/core.py23
-rw-r--r--lib/spack/spack/ci.py2
-rw-r--r--lib/spack/spack/cmd/bootstrap.py8
-rw-r--r--lib/spack/spack/cmd/buildcache.py252
-rw-r--r--lib/spack/spack/cmd/ci.py3
-rw-r--r--lib/spack/spack/cmd/common/arguments.py41
-rw-r--r--lib/spack/spack/cmd/mirror.py5
-rw-r--r--lib/spack/spack/mirror.py76
-rw-r--r--lib/spack/spack/schema/mirrors.py2
-rw-r--r--lib/spack/spack/test/ci.py16
-rw-r--r--lib/spack/spack/test/cmd/buildcache.py11
-rw-r--r--lib/spack/spack/test/cmd/ci.py2
-rw-r--r--lib/spack/spack/test/cmd/gpg.py23
-rw-r--r--lib/spack/spack/test/cmd/mirror.py65
-rw-r--r--lib/spack/spack/util/url.py14
-rwxr-xr-xshare/spack/spack-completion.bash16
17 files changed, 353 insertions, 316 deletions
diff --git a/lib/spack/docs/binary_caches.rst b/lib/spack/docs/binary_caches.rst
index 7c41485df8..fe73e79222 100644
--- a/lib/spack/docs/binary_caches.rst
+++ b/lib/spack/docs/binary_caches.rst
@@ -13,49 +13,51 @@ Some sites may encourage users to set up their own test environments
before carrying out central installations, or some users may prefer to set
up these environments on their own motivation. To reduce the load of
recompiling otherwise identical package specs in different installations,
-installed packages can be put into build cache tarballs, uploaded to
+installed packages can be put into build cache tarballs, pushed to
your Spack mirror and then downloaded and installed by others.
+Whenever a mirror provides prebuilt packages, Spack will take these packages
+into account during concretization and installation, making ``spack install``
+signficantly faster.
---------------------------
-Creating build cache files
---------------------------
-A compressed tarball of an installed package is created. Tarballs are created
-for all of its link and run dependency packages as well. Compressed tarballs are
-signed with gpg and signature and tarball and put in a ``.spack`` file. Optionally,
-the rpaths (and ids and deps on macOS) can be changed to paths relative to
-the Spack install tree before the tarball is created.
+.. note::
+
+ We use the terms "build cache" and "mirror" often interchangeably. Mirrors
+ are used during installation both for sources and prebuilt packages. Build
+ caches refer to mirrors that provide prebuilt packages.
+
+
+----------------------
+Creating a build cache
+----------------------
Build caches are created via:
.. code-block:: console
- $ spack buildcache create <spec>
+ $ spack buildcache create <path/url/mirror name> <spec>
+This command takes the locally installed spec and its dependencies, and
+creates tarballs of their install prefixes. It also generates metadata files,
+signed with GPG. These tarballs and metadata files are then pushed to the
+provided binary cache, which can be a local directory or a remote URL.
-If you wanted to create a build cache in a local directory, you would provide
-the ``-d`` argument to target that directory, again also specifying the spec.
-Here is an example creating a local directory, "spack-cache" and creating
-build cache files for the "ninja" spec:
+Here is an example where a build cache is created in a local directory named
+"spack-cache", to which we push the "ninja" spec:
.. code-block:: console
- $ mkdir -p ./spack-cache
- $ spack buildcache create -d ./spack-cache ninja
- ==> Buildcache files will be output to file:///home/spackuser/spack/spack-cache/build_cache
- gpgconf: socketdir is '/run/user/1000/gnupg'
- gpg: using "E6DF6A8BD43208E4D6F392F23777740B7DBD643D" as default secret key for signing
+ $ spack buildcache create --allow-root ./spack-cache ninja
+ ==> Pushing binary packages to file:///home/spackuser/spack/spack-cache/build_cache
-Note that the targeted spec must already be installed. Once you have a build cache,
-you can add it as a mirror, discussed next.
+Not that ``ninja`` must be installed locally for this to work.
-.. warning::
+We're using the ``--allow-root`` flag to tell Spack that is OK when any of
+the binaries we're pushing contain references to the local Spack install
+directory.
- Spack improved the format used for binary caches in v0.18. The entire v0.18 series
- will be able to verify and install binary caches both in the new and in the old format.
- Support for using the old format is expected to end in v0.19, so we advise users to
- recreate relevant buildcaches using Spack v0.18 or higher.
+Once you have a build cache, you can add it as a mirror, discussed next.
---------------------------------------
Finding or installing build cache files
@@ -66,10 +68,10 @@ with:
.. code-block:: console
- $ spack mirror add <name> <url>
+ $ spack mirror add <name> <url or path>
-Note that the url can be a web url _or_ a local filesystem location. In the previous
+Both web URLs and local paths on the filesystem can be specified. In the previous
example, you might add the directory "spack-cache" and call it ``mymirror``:
@@ -94,7 +96,7 @@ this new build cache as follows:
.. code-block:: console
- $ spack buildcache update-index -d spack-cache/
+ $ spack buildcache update-index ./spack-cache
Now you can use list:
@@ -105,46 +107,38 @@ Now you can use list:
-- linux-ubuntu20.04-skylake / gcc@9.3.0 ------------------------
ninja@1.10.2
-
-Great! So now let's say you have a different spack installation, or perhaps just
-a different environment for the same one, and you want to install a package from
-that build cache. Let's first uninstall the actual library "ninja" to see if we can
-re-install it from the cache.
+With ``mymirror`` configured and an index available, Spack will automatically
+use it during concretization and installation. That means that you can expect
+``spack install ninja`` to fetch prebuilt packages from the mirror. Let's
+verify by re-installing ninja:
.. code-block:: console
$ spack uninstall ninja
-
-
-And now reinstall from the buildcache
-
-.. code-block:: console
-
- $ spack buildcache install ninja
- ==> buildcache spec(s) matching ninja
- ==> Fetching file:///home/spackuser/spack/spack-cache/build_cache/linux-ubuntu20.04-skylake/gcc-9.3.0/ninja-1.10.2/linux-ubuntu20.04-skylake-gcc-9.3.0-ninja-1.10.2-i4e5luour7jxdpc3bkiykd4imke3mkym.spack
- ####################################################################################################################################### 100.0%
- ==> Installing buildcache for spec ninja@1.10.2%gcc@9.3.0 arch=linux-ubuntu20.04-skylake
- gpgconf: socketdir is '/run/user/1000/gnupg'
- gpg: Signature made Tue 23 Mar 2021 10:16:29 PM MDT
- gpg: using RSA key E6DF6A8BD43208E4D6F392F23777740B7DBD643D
- gpg: Good signature from "spackuser (GPG created for Spack) <spackuser@noreply.users.github.com>" [ultimate]
+ $ spack install ninja
+ ==> Installing ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz
+ ==> Fetching file:///home/spackuser/spack/spack-cache/build_cache/linux-ubuntu20.04-skylake-gcc-9.3.0-ninja-1.10.2-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spec.json.sig
+ gpg: Signature made Do 12 Jan 2023 16:01:04 CET
+ gpg: using RSA key 61B82B2B2350E171BD17A1744E3A689061D57BF6
+ gpg: Good signature from "example (GPG created for Spack) <example@example.com>" [ultimate]
+ ==> Fetching file:///home/spackuser/spack/spack-cache/build_cache/linux-ubuntu20.04-skylake/gcc-9.3.0/ninja-1.10.2/linux-ubuntu20.04-skylake-gcc-9.3.0-ninja-1.10.2-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spack
+ ==> Extracting ninja-1.10.2-yxferyhmrjkosgta5ei6b4lqf6bxbscz from binary cache
+ ==> ninja: Successfully installed ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz
+ Search: 0.00s. Fetch: 0.17s. Install: 0.12s. Total: 0.29s
+ [+] /home/harmen/spack/opt/spack/linux-ubuntu20.04-skylake/gcc-9.3.0/ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz
It worked! You've just completed a full example of creating a build cache with
-a spec of interest, adding it as a mirror, updating it's index, listing the contents,
+a spec of interest, adding it as a mirror, updating its index, listing the contents,
and finally, installing from it.
-
-Note that the above command is intended to install a particular package to a
-build cache you have created, and not to install a package from a build cache.
-For the latter, once a mirror is added, by default when you do ``spack install`` the ``--use-cache``
-flag is set, and you will install a package from a build cache if it is available.
-If you want to always use the cache, you can do:
+By default Spack falls back to building from sources when the mirror is not available
+or when the package is simply not already available. To force Spack to only install
+prebuilt packages, you can use
.. code-block:: console
- $ spack install --cache-only <package>
+ $ spack install --use-buildcache only <package>
For example, to combine all of the commands above to add the E4S build cache
and then install from it exclusively, you would do:
@@ -153,7 +147,7 @@ and then install from it exclusively, you would do:
$ spack mirror add E4S https://cache.e4s.io
$ spack buildcache keys --install --trust
- $ spack install --cache-only <package>
+ $ spack install --use-buildache only <package>
We use ``--install`` and ``--trust`` to say that we are installing keys to our
keyring, and trusting all downloaded keys.
diff --git a/lib/spack/spack/bootstrap/core.py b/lib/spack/spack/bootstrap/core.py
index f4b435deba..4d87212b81 100644
--- a/lib/spack/spack/bootstrap/core.py
+++ b/lib/spack/spack/bootstrap/core.py
@@ -94,22 +94,15 @@ class Bootstrapper:
def __init__(self, conf):
self.conf = conf
self.name = conf["name"]
- self.url = conf["info"]["url"]
self.metadata_dir = spack.util.path.canonicalize_path(conf["metadata"])
- @property
- def mirror_url(self):
- """Mirror url associated with this bootstrapper"""
- # Absolute paths
- if os.path.isabs(self.url):
- return spack.util.url.format(self.url)
-
- # Check for :// and assume it's an url if we find it
- if "://" in self.url:
- return self.url
-
- # Otherwise, it's a relative path
- return spack.util.url.format(os.path.join(self.metadata_dir, self.url))
+ # Promote (relative) paths to file urls
+ url = conf["info"]["url"]
+ if spack.util.url.is_path_instead_of_url(url):
+ if not os.path.isabs(url):
+ url = os.path.join(self.metadata_dir, url)
+ url = spack.util.url.path_to_file_url(url)
+ self.url = url
@property
def mirror_scope(self):
@@ -117,7 +110,7 @@ class Bootstrapper:
this bootstrapper.
"""
return spack.config.InternalConfigScope(
- self.config_scope_name, {"mirrors:": {self.name: self.mirror_url}}
+ self.config_scope_name, {"mirrors:": {self.name: self.url}}
)
def try_import(self, module: str, abstract_spec_str: str) -> bool:
diff --git a/lib/spack/spack/ci.py b/lib/spack/spack/ci.py
index 1129587b84..11b5f608d6 100644
--- a/lib/spack/spack/ci.py
+++ b/lib/spack/spack/ci.py
@@ -1482,7 +1482,7 @@ def _push_mirror_contents(env, specfile_path, sign_binaries, mirror_url):
tty.debug("Creating buildcache ({0})".format("unsigned" if unsigned else "signed"))
hashes = env.all_hashes() if env else None
matches = spack.store.specfile_matches(specfile_path, hashes=hashes)
- push_url = spack.mirror.push_url_from_mirror_url(mirror_url)
+ push_url = spack.mirror.Mirror.from_url(mirror_url).push_url
spec_kwargs = {"include_root": True, "include_dependencies": False}
kwargs = {"force": True, "allow_root": True, "unsigned": unsigned}
bindist.push(matches, push_url, spec_kwargs, **kwargs)
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index bef1979f0a..f21e12c929 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -43,6 +43,8 @@ BINARY_METADATA = {
"The sha256 checksum of binaries is checked before installation."
),
"info": {
+ # This is a mis-nomer since it's not a URL; but file urls cannot
+ # represent relative paths, so we have to live with it for now.
"url": os.path.join("..", "..", LOCAL_MIRROR_DIR),
"homepage": "https://github.com/spack/spack-bootstrap-mirrors",
"releases": "https://github.com/spack/spack-bootstrap-mirrors/releases",
@@ -58,7 +60,11 @@ PATCHELF_JSON = "$spack/share/spack/bootstrap/github-actions-v0.4/patchelf.json"
SOURCE_METADATA = {
"type": "install",
"description": "Mirror with software needed to bootstrap Spack",
- "info": {"url": os.path.join("..", "..", LOCAL_MIRROR_DIR)},
+ "info": {
+ # This is a mis-nomer since it's not a URL; but file urls cannot
+ # represent relative paths, so we have to live with it for now.
+ "url": os.path.join("..", "..", LOCAL_MIRROR_DIR)
+ },
}
diff --git a/lib/spack/spack/cmd/buildcache.py b/lib/spack/spack/cmd/buildcache.py
index 53fe50c64a..3d0d734cb4 100644
--- a/lib/spack/spack/cmd/buildcache.py
+++ b/lib/spack/spack/cmd/buildcache.py
@@ -8,7 +8,6 @@ import os
import shutil
import sys
import tempfile
-import urllib.parse
import llnl.util.tty as tty
@@ -66,27 +65,37 @@ def setup_parser(subparser):
create.add_argument(
"-k", "--key", metavar="key", type=str, default=None, help="Key for signing."
)
- output = create.add_mutually_exclusive_group(required=True)
+ output = create.add_mutually_exclusive_group(required=False)
+ # TODO: remove from Spack 0.21
output.add_argument(
"-d",
"--directory",
metavar="directory",
- type=str,
- help="local directory where buildcaches will be written.",
+ dest="mirror_flag",
+ type=arguments.mirror_directory,
+ help="local directory where buildcaches will be written. (deprecated)",
)
+ # TODO: remove from Spack 0.21
output.add_argument(
"-m",
"--mirror-name",
metavar="mirror-name",
- type=str,
- help="name of the mirror where buildcaches will be written.",
+ dest="mirror_flag",
+ type=arguments.mirror_name,
+ help="name of the mirror where buildcaches will be written. (deprecated)",
)
+ # TODO: remove from Spack 0.21
output.add_argument(
"--mirror-url",
metavar="mirror-url",
- type=str,
- help="URL of the mirror where buildcaches will be written.",
- )
+ dest="mirror_flag",
+ type=arguments.mirror_url,
+ help="URL of the mirror where buildcaches will be written. (deprecated)",
+ )
+ # Unfortunately we cannot add this to the mutually exclusive group above,
+ # because we have further positional arguments.
+ # TODO: require from Spack 0.21
+ create.add_argument("mirror", type=str, help="Mirror name, path, or URL.", nargs="?")
create.add_argument(
"--rebuild-index",
action="store_true",
@@ -179,7 +188,7 @@ def setup_parser(subparser):
"-m",
"--mirror-url",
default=None,
- help="Override any configured mirrors with this mirror url",
+ help="Override any configured mirrors with this mirror URL",
)
check.add_argument(
@@ -266,55 +275,108 @@ def setup_parser(subparser):
help="A quoted glob pattern identifying copy manifest files",
)
source = sync.add_mutually_exclusive_group(required=False)
+ # TODO: remove in Spack 0.21
+ source.add_argument(
+ "--src-directory",
+ metavar="DIRECTORY",
+ dest="src_mirror_flag",
+ type=arguments.mirror_directory,
+ help="Source mirror as a local file path (deprecated)",
+ )
+ # TODO: remove in Spack 0.21
source.add_argument(
- "--src-directory", metavar="DIRECTORY", type=str, help="Source mirror as a local file path"
+ "--src-mirror-name",
+ metavar="MIRROR_NAME",
+ dest="src_mirror_flag",
+ type=arguments.mirror_name,
+ help="Name of the source mirror (deprecated)",
)
+ # TODO: remove in Spack 0.21
source.add_argument(
- "--src-mirror-name", metavar="MIRROR_NAME", type=str, help="Name of the source mirror"
+ "--src-mirror-url",
+ metavar="MIRROR_URL",
+ dest="src_mirror_flag",
+ type=arguments.mirror_url,
+ help="URL of the source mirror (deprecated)",
)
+ # TODO: only support this in 0.21
source.add_argument(
- "--src-mirror-url", metavar="MIRROR_URL", type=str, help="URL of the source mirror"
+ "src_mirror",
+ metavar="source mirror",
+ type=arguments.mirror_name_or_url,
+ help="Source mirror name, path, or URL",
+ nargs="?",
)
dest = sync.add_mutually_exclusive_group(required=False)
+ # TODO: remove in Spack 0.21
dest.add_argument(
"--dest-directory",
metavar="DIRECTORY",
- type=str,
- help="Destination mirror as a local file path",
+ dest="dest_mirror_flag",
+ type=arguments.mirror_directory,
+ help="Destination mirror as a local file path (deprecated)",
)
+ # TODO: remove in Spack 0.21
dest.add_argument(
"--dest-mirror-name",
metavar="MIRROR_NAME",
- type=str,
- help="Name of the destination mirror",
+ type=arguments.mirror_name,
+ dest="dest_mirror_flag",
+ help="Name of the destination mirror (deprecated)",
+ )
+ # TODO: remove in Spack 0.21
+ dest.add_argument(
+ "--dest-mirror-url",
+ metavar="MIRROR_URL",
+ dest="dest_mirror_flag",
+ type=arguments.mirror_url,
+ help="URL of the destination mirror (deprecated)",
)
+ # TODO: only support this in 0.21
dest.add_argument(
- "--dest-mirror-url", metavar="MIRROR_URL", type=str, help="URL of the destination mirror"
+ "dest_mirror",
+ metavar="destination mirror",
+ type=arguments.mirror_name_or_url,
+ help="Destination mirror name, path, or URL",
+ nargs="?",
)
sync.set_defaults(func=sync_fn)
# Update buildcache index without copying any additional packages
update_index = subparsers.add_parser("update-index", help=update_index_fn.__doc__)
update_index_out = update_index.add_mutually_exclusive_group(required=True)
+ # TODO: remove in Spack 0.21
update_index_out.add_argument(
"-d",
"--directory",
metavar="directory",
- type=str,
- help="local directory where buildcaches will be written.",
+ dest="mirror_flag",
+ type=arguments.mirror_directory,
+ help="local directory where buildcaches will be written (deprecated)",
)
+ # TODO: remove in Spack 0.21
update_index_out.add_argument(
"-m",
"--mirror-name",
metavar="mirror-name",
- type=str,
- help="name of the mirror where buildcaches will be written.",
+ dest="mirror_flag",
+ type=arguments.mirror_name,
+ help="name of the mirror where buildcaches will be written (deprecated)",
)
+ # TODO: remove in Spack 0.21
update_index_out.add_argument(
"--mirror-url",
metavar="mirror-url",
- type=str,
- help="URL of the mirror where buildcaches will be written.",
+ dest="mirror_flag",
+ type=arguments.mirror_url,
+ help="URL of the mirror where buildcaches will be written (deprecated)",
+ )
+ # TODO: require from Spack 0.21
+ update_index_out.add_argument(
+ "mirror",
+ type=arguments.mirror_name_or_url,
+ help="Destination mirror name, path, or URL",
+ nargs="?",
)
update_index.add_argument(
"-k",
@@ -326,26 +388,17 @@ def setup_parser(subparser):
update_index.set_defaults(func=update_index_fn)
-def _mirror_url_from_args(args):
- if args.directory:
- return spack.mirror.push_url_from_directory(args.directory)
- if args.mirror_name:
- return spack.mirror.push_url_from_mirror_name(args.mirror_name)
- if args.mirror_url:
- return spack.mirror.push_url_from_mirror_url(args.mirror_url)
-
-
-def _matching_specs(args):
+def _matching_specs(specs, spec_file):
"""Return a list of matching specs read from either a spec file (JSON or YAML),
a query over the store or a query over the active environment.
"""
env = ev.active_environment()
hashes = env.all_hashes() if env else None
- if args.spec_file:
- return spack.store.specfile_matches(args.spec_file, hashes=hashes)
+ if spec_file:
+ return spack.store.specfile_matches(spec_file, hashes=hashes)
- if args.specs:
- constraints = spack.cmd.parse_specs(args.specs)
+ if specs:
+ constraints = spack.cmd.parse_specs(specs)
return spack.store.find(constraints, hashes=hashes)
if env:
@@ -383,10 +436,30 @@ def _concrete_spec_from_args(args):
def create_fn(args):
"""create a binary package and push it to a mirror"""
- push_url = _mirror_url_from_args(args)
- matches = _matching_specs(args)
+ if args.mirror_flag:
+ mirror = args.mirror_flag
+ elif not args.mirror:
+ raise ValueError("No mirror provided")
+ else:
+ mirror = arguments.mirror_name_or_url(args.mirror)
+
+ if args.mirror_flag:
+ tty.warn(
+ "Using flags to specify mirrors is deprecated and will be removed in "
+ "Spack 0.21, use positional arguments instead."
+ )
+
+ # TODO: remove this in 0.21. If we have mirror_flag, the first
+ # spec is in the positional mirror arg due to argparse limitations.
+ specs = args.specs
+ if args.mirror_flag and args.mirror:
+ specs.insert(0, args.mirror)
+
+ url = mirror.push_url
+
+ matches = _matching_specs(specs, args.spec_file)
- msg = "Pushing binary packages to {0}/build_cache".format(push_url)
+ msg = "Pushing binary packages to {0}/build_cache".format(url)
tty.msg(msg)
specs_kwargs = {
"include_root": "package" in args.things_to_install,
@@ -400,7 +473,7 @@ def create_fn(args):
"allow_root": args.allow_root,
"regenerate_index": args.rebuild_index,
}
- bindist.push(matches, push_url, specs_kwargs, **kwargs)
+ bindist.push(matches, url, specs_kwargs, **kwargs)
def install_fn(args):
@@ -593,51 +666,24 @@ def sync_fn(args):
manifest_copy(glob.glob(args.manifest_glob))
return 0
- # Figure out the source mirror
- source_location = None
- if args.src_directory:
- source_location = args.src_directory
- scheme = urllib.parse.urlparse(source_location, scheme="<missing>").scheme
- if scheme != "<missing>":
- raise ValueError('"--src-directory" expected a local path; got a URL, instead')
- # Ensure that the mirror lookup does not mistake this for named mirror
- source_location = url_util.path_to_file_url(source_location)
- elif args.src_mirror_name:
- source_location = args.src_mirror_name
- result = spack.mirror.MirrorCollection().lookup(source_location)
- if result.name == "<unnamed>":
- raise ValueError('no configured mirror named "{name}"'.format(name=source_location))
- elif args.src_mirror_url:
- source_location = args.src_mirror_url
- scheme = urllib.parse.urlparse(source_location, scheme="<missing>").scheme
- if scheme == "<missing>":
- raise ValueError('"{url}" is not a valid URL'.format(url=source_location))
-
- src_mirror = spack.mirror.MirrorCollection().lookup(source_location)
- src_mirror_url = url_util.format(src_mirror.fetch_url)
-
- # Figure out the destination mirror
- dest_location = None
- if args.dest_directory:
- dest_location = args.dest_directory
- scheme = urllib.parse.urlparse(dest_location, scheme="<missing>").scheme
- if scheme != "<missing>":
- raise ValueError('"--dest-directory" expected a local path; got a URL, instead')
- # Ensure that the mirror lookup does not mistake this for named mirror
- dest_location = url_util.path_to_file_url(dest_location)
- elif args.dest_mirror_name:
- dest_location = args.dest_mirror_name
- result = spack.mirror.MirrorCollection().lookup(dest_location)
- if result.name == "<unnamed>":
- raise ValueError('no configured mirror named "{name}"'.format(name=dest_location))
- elif args.dest_mirror_url:
- dest_location = args.dest_mirror_url
- scheme = urllib.parse.urlparse(dest_location, scheme="<missing>").scheme
- if scheme == "<missing>":
- raise ValueError('"{url}" is not a valid URL'.format(url=dest_location))
-
- dest_mirror = spack.mirror.MirrorCollection().lookup(dest_location)
- dest_mirror_url = url_util.format(dest_mirror.fetch_url)
+ # If no manifest_glob, require a source and dest mirror.
+ # TODO: Simplify in Spack 0.21
+ if not (args.src_mirror_flag or args.src_mirror) or not (
+ args.dest_mirror_flag or args.dest_mirror
+ ):
+ raise ValueError("Source and destination mirror are required.")
+
+ if args.src_mirror_flag or args.dest_mirror_flag:
+ tty.warn(
+ "Using flags to specify mirrors is deprecated and will be removed in "
+ "Spack 0.21, use positional arguments instead."
+ )
+
+ src_mirror = args.src_mirror_flag if args.src_mirror_flag else args.src_mirror
+ dest_mirror = args.dest_mirror_flag if args.dest_mirror_flag else args.dest_mirror
+
+ src_mirror_url = src_mirror.fetch_url
+ dest_mirror_url = dest_mirror.push_url
# Get the active environment
env = spack.cmd.require_active_env(cmd_name="buildcache sync")
@@ -698,38 +744,28 @@ def manifest_copy(manifest_file_list):
copy_buildcache_file(copy_file["src"], copy_file["dest"])
-def update_index(mirror_url, update_keys=False):
- mirror = spack.mirror.MirrorCollection().lookup(mirror_url)
- outdir = url_util.format(mirror.push_url)
+def update_index(mirror: spack.mirror.Mirror, update_keys=False):
+ url = mirror.push_url
- bindist.generate_package_index(url_util.join(outdir, bindist.build_cache_relative_path()))
+ bindist.generate_package_index(url_util.join(url, bindist.build_cache_relative_path()))
if update_keys:
keys_url = url_util.join(
- outdir, bindist.build_cache_relative_path(), bindist.build_cache_keys_relative_path()
+ url, bindist.build_cache_relative_path(), bindist.build_cache_keys_relative_path()
)
bindist.generate_key_index(keys_url)
-def _mirror_url_from_args_deprecated_format(args):
- # In Spack 0.19 the -d flag was equivalent to --mirror-url.
- # Spack 0.20 deprecates this, so in 0.21 -d means --directory.
- if args.directory and url_util.validate_scheme(urllib.parse.urlparse(args.directory).scheme):
- tty.warn(
- "Passing a URL to `update-index -d <url>` is deprecated "
- "and will be removed in Spack 0.21. "
- "Use `update-index --mirror-url <url>` instead."
- )
- return spack.mirror.push_url_from_mirror_url(args.directory)
- else:
- return _mirror_url_from_args(args)
-
-
def update_index_fn(args):
"""Update a buildcache index."""
- push_url = _mirror_url_from_args_deprecated_format(args)
- update_index(push_url, update_keys=args.keys)
+ if args.mirror_flag:
+ tty.warn(
+ "Using flags to specify mirrors is deprecated and will be removed in "
+ "Spack 0.21, use positional arguments instead."
+ )
+ mirror = args.mirror_flag if args.mirror_flag else args.mirror
+ update_index(mirror, update_keys=args.keys)
def buildcache(parser, args):
diff --git a/lib/spack/spack/cmd/ci.py b/lib/spack/spack/cmd/ci.py
index 4915e12ffb..54770f6ae3 100644
--- a/lib/spack/spack/cmd/ci.py
+++ b/lib/spack/spack/cmd/ci.py
@@ -241,8 +241,9 @@ def ci_reindex(args):
ci_mirrors = yaml_root["mirrors"]
mirror_urls = [url for url in ci_mirrors.values()]
remote_mirror_url = mirror_urls[0]
+ mirror = spack.mirror.Mirror(remote_mirror_url)
- buildcache.update_index(remote_mirror_url, update_keys=True)
+ buildcache.update_index(mirror, update_keys=True)
def ci_rebuild(args):
diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
index 42c5d611b2..7f52037f82 100644
--- a/lib/spack/spack/cmd/common/arguments.py
+++ b/lib/spack/spack/cmd/common/arguments.py
@@ -5,6 +5,7 @@
import argparse
+import os.path
from llnl.util.lang import stable_partition
@@ -12,6 +13,7 @@ import spack.cmd
import spack.config
import spack.dependency as dep
import spack.environment as ev
+import spack.mirror
import spack.modules
import spack.reporters
import spack.spec
@@ -552,3 +554,42 @@ def use_buildcache(cli_arg_value):
dependencies = val
return package, dependencies
+
+
+def mirror_name_or_url(m):
+ # Look up mirror by name or use anonymous mirror with path/url.
+ # We want to guard against typos in mirror names, to avoid pushing
+ # accidentally to a dir in the current working directory.
+
+ # If there's a \ or / in the name, it's interpreted as a path or url.
+ if "/" in m or "\\" in m:
+ return spack.mirror.Mirror(m)
+
+ # Otherwise, the named mirror is required to exist.
+ try:
+ return spack.mirror.require_mirror_name(m)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(
+ str(e) + ". Did you mean {}?".format(os.path.join(".", m))
+ )
+
+
+def mirror_url(url):
+ try:
+ return spack.mirror.Mirror.from_url(url)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(str(e))
+
+
+def mirror_directory(path):
+ try:
+ return spack.mirror.Mirror.from_local_path(path)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(str(e))
+
+
+def mirror_name(name):
+ try:
+ return spack.mirror.require_mirror_name(name)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(str(e))
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
index 7768d1678c..5c5ae7abc9 100644
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -145,8 +145,7 @@ def setup_parser(subparser):
def mirror_add(args):
"""Add a mirror to Spack."""
- url = url_util.format(args.url)
- spack.mirror.add(args.name, url, args.scope, args)
+ spack.mirror.add(args.name, args.url, args.scope, args)
def mirror_remove(args):
@@ -156,7 +155,7 @@ def mirror_remove(args):
def mirror_set_url(args):
"""Change the URL of a mirror."""
- url = url_util.format(args.url)
+ url = args.url
mirrors = spack.config.get("mirrors", scope=args.scope)
if not mirrors:
mirrors = syaml_dict()
diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py
index d28b52c9c1..2b741b06ad 100644
--- a/lib/spack/spack/mirror.py
+++ b/lib/spack/spack/mirror.py
@@ -31,15 +31,15 @@ import spack.fetch_strategy as fs
import spack.mirror
import spack.spec
import spack.url as url
+import spack.util.path
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
import spack.util.url as url_util
from spack.util.spack_yaml import syaml_dict
from spack.version import VersionList
-
-def _is_string(url):
- return isinstance(url, str)
+#: What schemes do we support
+supported_url_schemes = ("file", "http", "https", "sftp", "ftp", "s3", "gs")
def _display_mirror_entry(size, name, url, type_=None):
@@ -51,6 +51,19 @@ def _display_mirror_entry(size, name, url, type_=None):
print("%-*s%s%s" % (size + 4, name, url, type_))
+def _url_or_path_to_url(url_or_path: str) -> str:
+ """For simplicity we allow mirror URLs in config files to be local, relative paths.
+ This helper function takes care of distinguishing between URLs and paths, and
+ canonicalizes paths before transforming them into file:// URLs."""
+ # Is it a supported URL already? Then don't do path-related canonicalization.
+ parsed = urllib.parse.urlparse(url_or_path)
+ if parsed.scheme in supported_url_schemes:
+ return url_or_path
+
+ # Otherwise we interpret it as path, and we should promote it to file:// URL.
+ return url_util.path_to_file_url(spack.util.path.canonicalize_path(url_or_path))
+
+
class Mirror(object):
"""Represents a named location for storing source tarballs and binary
packages.
@@ -90,6 +103,21 @@ class Mirror(object):
except Exception as e:
raise sjson.SpackJSONError("error parsing JSON mirror:", str(e)) from e
+ @staticmethod
+ def from_local_path(path: str):
+ return Mirror(fetch_url=url_util.path_to_file_url(path))
+
+ @staticmethod
+ def from_url(url: str):
+ """Create an anonymous mirror by URL. This method validates the URL."""
+ if not urllib.parse.urlparse(url).scheme in supported_url_schemes:
+ raise ValueError(
+ '"{}" is not a valid mirror URL. Scheme must be once of {}.'.format(
+ url, ", ".join(supported_url_schemes)
+ )
+ )
+ return Mirror(fetch_url=url)
+
def to_dict(self):
if self._push_url is None:
return syaml_dict([("fetch", self._fetch_url), ("push", self._fetch_url)])
@@ -201,7 +229,11 @@ class Mirror(object):
@property
def fetch_url(self):
- return self._fetch_url if _is_string(self._fetch_url) else self._fetch_url["url"]
+ """Get the valid, canonicalized fetch URL"""
+ url_or_path = (
+ self._fetch_url if isinstance(self._fetch_url, str) else self._fetch_url["url"]
+ )
+ return _url_or_path_to_url(url_or_path)
@fetch_url.setter
def fetch_url(self, url):
@@ -210,9 +242,12 @@ class Mirror(object):
@property
def push_url(self):
+ """Get the valid, canonicalized push URL. Returns fetch URL if no custom
+ push URL is defined"""
if self._push_url is None:
- return self._fetch_url if _is_string(self._fetch_url) else self._fetch_url["url"]
- return self._push_url if _is_string(self._push_url) else self._push_url["url"]
+ return self.fetch_url
+ url_or_path = self._push_url if isinstance(self._push_url, str) else self._push_url["url"]
+ return _url_or_path_to_url(url_or_path)
@push_url.setter
def push_url(self, url):
@@ -663,31 +698,12 @@ def create_mirror_from_package_object(pkg_obj, mirror_cache, mirror_stats):
return True
-def push_url_from_directory(output_directory):
- """Given a directory in the local filesystem, return the URL on
- which to push binary packages.
- """
- if url_util.validate_scheme(urllib.parse.urlparse(output_directory).scheme):
- raise ValueError("expected a local path, but got a URL instead")
- mirror_url = url_util.path_to_file_url(output_directory)
- mirror = spack.mirror.MirrorCollection().lookup(mirror_url)
- return url_util.format(mirror.push_url)
-
-
-def push_url_from_mirror_name(mirror_name):
- """Given a mirror name, return the URL on which to push binary packages."""
- mirror = spack.mirror.MirrorCollection().lookup(mirror_name)
- if mirror.name == "<unnamed>":
+def require_mirror_name(mirror_name):
+ """Find a mirror by name and raise if it does not exist"""
+ mirror = spack.mirror.MirrorCollection().get(mirror_name)
+ if not mirror:
raise ValueError('no mirror named "{0}"'.format(mirror_name))
- return url_util.format(mirror.push_url)
-
-
-def push_url_from_mirror_url(mirror_url):
- """Given a mirror URL, return the URL on which to push binary packages."""
- if not url_util.validate_scheme(urllib.parse.urlparse(mirror_url).scheme):
- raise ValueError('"{0}" is not a valid URL'.format(mirror_url))
- mirror = spack.mirror.MirrorCollection().lookup(mirror_url)
- return url_util.format(mirror.push_url)
+ return mirror
class MirrorError(spack.error.SpackError):
diff --git a/lib/spack/spack/schema/mirrors.py b/lib/spack/spack/schema/mirrors.py
index eb00a699f0..f469444247 100644
--- a/lib/spack/spack/schema/mirrors.py
+++ b/lib/spack/spack/schema/mirrors.py
@@ -6,10 +6,8 @@
"""Schema for mirrors.yaml configuration file.
.. literalinclude:: _spack_root/lib/spack/spack/schema/mirrors.py
- :lines: 13-
"""
-
#: Properties for inclusion in other schemas
properties = {
"mirrors": {
diff --git a/lib/spack/spack/test/ci.py b/lib/spack/spack/test/ci.py
index fb21d90773..e2c5ca92eb 100644
--- a/lib/spack/spack/test/ci.py
+++ b/lib/spack/spack/test/ci.py
@@ -23,22 +23,6 @@ import spack.util.gpg
import spack.util.spack_yaml as syaml
-@pytest.fixture
-def tmp_scope():
- """Creates a temporary configuration scope"""
- base_name = "internal-testing-scope"
- current_overrides = set(x.name for x in cfg.config.matching_scopes(r"^{0}".format(base_name)))
-
- num_overrides = 0
- scope_name = base_name
- while scope_name in current_overrides:
- scope_name = "{0}{1}".format(base_name, num_overrides)
- num_overrides += 1
-
- with cfg.override(cfg.InternalConfigScope(scope_name)):
- yield scope_name
-
-
def test_urlencode_string():
s = "Spack Test Project"
diff --git a/lib/spack/spack/test/cmd/buildcache.py b/lib/spack/spack/test/cmd/buildcache.py
index bedb662b9d..d5d12bc08c 100644
--- a/lib/spack/spack/test/cmd/buildcache.py
+++ b/lib/spack/spack/test/cmd/buildcache.py
@@ -3,7 +3,6 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
-import argparse
import errno
import os
import platform
@@ -268,13 +267,3 @@ def test_buildcache_create_install(
tarball = spack.binary_distribution.tarball_name(spec, ".spec.json")
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball_path))
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball))
-
-
-def test_deprecation_mirror_url_dir_flag(capfd):
- # Test that passing `update-index -d <url>` gives a deprecation warning.
- parser = argparse.ArgumentParser()
- spack.cmd.buildcache.setup_parser(parser)
- url = spack.util.url.path_to_file_url(os.getcwd())
- args = parser.parse_args(["update-index", "-d", url])
- spack.cmd.buildcache._mirror_url_from_args_deprecated_format(args)
- assert "Passing a URL to `update-index -d <url>` is deprecated" in capfd.readouterr()[1]
diff --git a/lib/spack/spack/test/cmd/ci.py b/lib/spack/spack/test/cmd/ci.py
index 3033862c9b..73fae60b91 100644
--- a/lib/spack/spack/test/cmd/ci.py
+++ b/lib/spack/spack/test/cmd/ci.py
@@ -1218,7 +1218,7 @@ def test_push_mirror_contents(
working_dir = tmpdir.join("working_dir")
mirror_dir = working_dir.join("mirror")
- mirror_url = "file://{0}".format(mirror_dir.strpath)
+ mirror_url = url_util.path_to_file_url(mirror_dir.strpath)
ci.import_signing_key(_signing_key())
diff --git a/lib/spack/spack/test/cmd/gpg.py b/lib/spack/spack/test/cmd/gpg.py
index 9d698275ed..d5bcd91255 100644
--- a/lib/spack/spack/test/cmd/gpg.py
+++ b/lib/spack/spack/test/cmd/gpg.py
@@ -25,25 +25,6 @@ mirror = SpackCommand("mirror")
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-@pytest.fixture
-def tmp_scope():
- """Creates a temporary configuration scope"""
-
- base_name = "internal-testing-scope"
- current_overrides = set(
- x.name for x in spack.config.config.matching_scopes(r"^{0}".format(base_name))
- )
-
- num_overrides = 0
- scope_name = base_name
- while scope_name in current_overrides:
- scope_name = "{0}{1}".format(base_name, num_overrides)
- num_overrides += 1
-
- with spack.config.override(spack.config.InternalConfigScope(scope_name)):
- yield scope_name
-
-
# test gpg command detection
@pytest.mark.parametrize(
"cmd_name,version",
@@ -81,7 +62,7 @@ def test_no_gpg_in_path(tmpdir, mock_gnupghome, monkeypatch, mutable_config):
@pytest.mark.maybeslow
-def test_gpg(tmpdir, tmp_scope, mock_gnupghome):
+def test_gpg(tmpdir, mutable_config, mock_gnupghome):
# Verify a file with an empty keyring.
with pytest.raises(ProcessError):
gpg("verify", os.path.join(mock_gpg_data_path, "content.txt"))
@@ -211,6 +192,6 @@ def test_gpg(tmpdir, tmp_scope, mock_gnupghome):
test_path = tmpdir.join("named_cache")
os.makedirs("%s" % test_path)
mirror_url = "file://%s" % test_path
- mirror("add", "--scope", tmp_scope, "gpg", mirror_url)
+ mirror("add", "gpg", mirror_url)
gpg("publish", "--rebuild-index", "-m", "gpg")
assert os.path.exists("%s/build_cache/_pgp/index.json" % test_path)
diff --git a/lib/spack/spack/test/cmd/mirror.py b/lib/spack/spack/test/cmd/mirror.py
index c1a9830870..30697b4122 100644
--- a/lib/spack/spack/test/cmd/mirror.py
+++ b/lib/spack/spack/test/cmd/mirror.py
@@ -26,25 +26,6 @@ uninstall = SpackCommand("uninstall")
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-@pytest.fixture
-def tmp_scope():
- """Creates a temporary configuration scope"""
-
- base_name = "internal-testing-scope"
- current_overrides = set(
- x.name for x in spack.config.config.matching_scopes(r"^{0}".format(base_name))
- )
-
- num_overrides = 0
- scope_name = base_name
- while scope_name in current_overrides:
- scope_name = "{0}{1}".format(base_name, num_overrides)
- num_overrides += 1
-
- with spack.config.override(spack.config.InternalConfigScope(scope_name)):
- yield scope_name
-
-
@pytest.mark.disable_clean_stage_check
@pytest.mark.regression("8083")
def test_regression_8083(tmpdir, capfd, mock_packages, mock_fetch, config):
@@ -154,48 +135,44 @@ mpich@1.0
assert not any(spec.satisfies(y) for spec in mirror_specs for y in expected_exclude)
-def test_mirror_crud(tmp_scope, capsys):
+def test_mirror_crud(mutable_config, capsys):
with capsys.disabled():
- mirror("add", "--scope", tmp_scope, "mirror", "http://spack.io")
+ mirror("add", "mirror", "http://spack.io")
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
- mirror("add", "--scope", tmp_scope, "mirror", "http://spack.io")
+ mirror("add", "mirror", "http://spack.io")
# no-op
- output = mirror("set-url", "--scope", tmp_scope, "mirror", "http://spack.io")
+ output = mirror("set-url", "mirror", "http://spack.io")
assert "No changes made" in output
- output = mirror("set-url", "--scope", tmp_scope, "--push", "mirror", "s3://spack-public")
+ output = mirror("set-url", "--push", "mirror", "s3://spack-public")
assert "Changed (push) url" in output
# no-op
- output = mirror("set-url", "--scope", tmp_scope, "--push", "mirror", "s3://spack-public")
+ output = mirror("set-url", "--push", "mirror", "s3://spack-public")
assert "No changes made" in output
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
# Test S3 connection info token
mirror(
"add",
- "--scope",
- tmp_scope,
"--s3-access-token",
"aaaaaazzzzz",
"mirror",
"s3://spack-public",
)
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
# Test S3 connection info id/key
mirror(
"add",
- "--scope",
- tmp_scope,
"--s3-access-key-id",
"foo",
"--s3-access-key-secret",
@@ -204,14 +181,12 @@ def test_mirror_crud(tmp_scope, capsys):
"s3://spack-public",
)
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
# Test S3 connection info with endpoint URL
mirror(
"add",
- "--scope",
- tmp_scope,
"--s3-access-token",
"aaaaaazzzzz",
"--s3-endpoint-url",
@@ -220,32 +195,32 @@ def test_mirror_crud(tmp_scope, capsys):
"s3://spack-public",
)
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
- output = mirror("list", "--scope", tmp_scope)
+ output = mirror("list")
assert "No mirrors configured" in output
# Test GCS Mirror
- mirror("add", "--scope", tmp_scope, "mirror", "gs://spack-test")
+ mirror("add", "mirror", "gs://spack-test")
- output = mirror("remove", "--scope", tmp_scope, "mirror")
+ output = mirror("remove", "mirror")
assert "Removed mirror" in output
-def test_mirror_nonexisting(tmp_scope):
+def test_mirror_nonexisting(mutable_config):
with pytest.raises(SpackCommandError):
- mirror("remove", "--scope", tmp_scope, "not-a-mirror")
+ mirror("remove", "not-a-mirror")
with pytest.raises(SpackCommandError):
- mirror("set-url", "--scope", tmp_scope, "not-a-mirror", "http://spack.io")
+ mirror("set-url", "not-a-mirror", "http://spack.io")
-def test_mirror_name_collision(tmp_scope):
- mirror("add", "--scope", tmp_scope, "first", "1")
+def test_mirror_name_collision(mutable_config):
+ mirror("add", "first", "1")
with pytest.raises(SpackCommandError):
- mirror("add", "--scope", tmp_scope, "first", "1")
+ mirror("add", "first", "1")
def test_mirror_destroy(
diff --git a/lib/spack/spack/util/url.py b/lib/spack/spack/util/url.py
index 743ec3283e..6a2b6c4b2e 100644
--- a/lib/spack/spack/util/url.py
+++ b/lib/spack/spack/util/url.py
@@ -71,6 +71,20 @@ def file_url_string_to_path(url):
return urllib.request.url2pathname(urllib.parse.urlparse(url).path)
+def is_path_instead_of_url(path_or_url):
+ """Historically some config files and spack commands used paths
+ where urls should be used. This utility can be used to validate
+ and promote paths to urls."""
+ scheme = urllib.parse.urlparse(path_or_url).scheme
+
+ # On non-Windows, no scheme means it's likely a path
+ if not sys.platform == "win32":
+ return not scheme
+
+ # On Windows, we may have drive letters.
+ return "A" <= scheme <= "Z"
+
+
def format(parsed_url):
"""Format a URL string
diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash
index 8d1bd8ce98..18c6975d69 100755
--- a/share/spack/spack-completion.bash
+++ b/share/spack/spack-completion.bash
@@ -500,7 +500,7 @@ _spack_buildcache_create() {
then
SPACK_COMPREPLY="-h --help -r --rel -f --force -u --unsigned -a --allow-root -k --key -d --directory -m --mirror-name --mirror-url --rebuild-index --spec-file --only"
else
- _all_packages
+ _mirrors
fi
}
@@ -552,11 +552,21 @@ _spack_buildcache_save_specfile() {
}
_spack_buildcache_sync() {
- SPACK_COMPREPLY="-h --help --manifest-glob --src-directory --src-mirror-name --src-mirror-url --dest-directory --dest-mirror-name --dest-mirror-url"
+ if $list_options
+ then
+ SPACK_COMPREPLY="-h --help --manifest-glob --src-directory --src-mirror-name --src-mirror-url --dest-directory --dest-mirror-name --dest-mirror-url"
+ else
+ SPACK_COMPREPLY=""
+ fi
}
_spack_buildcache_update_index() {
- SPACK_COMPREPLY="-h --help -d --directory -m --mirror-name --mirror-url -k --keys"
+ if $list_options
+ then
+ SPACK_COMPREPLY="-h --help -d --directory -m --mirror-name --mirror-url -k --keys"
+ else
+ _mirrors
+ fi
}
_spack_cd() {