Repository: spack/spack

Issue:
Spack mirror mishandles paths
### Steps to reproduce

```
spack mirror create -d /some/absolute/path --all
```
What's happening here is that the Spack mirror command hands the -d/--directory argument off to `spack.util.url.path_to_file_url`, which converts the path into a `file:///some/absolute/path` URI. Then that URI gets passed to `spack.mirror.cache_and_stats`, which uses `os` lib functions to check the existence of that directory and create it if necessary. 

The problem is, `os` filesystem operations don't understand URI's (it interprets it as the relative path `file:/some/absolute/path`). Spack happily creates this directory and puts the mirror there. 

### Error message

None

### Information on your system

* **Spack:** 0.20.0.dev0
* **Python:** 3.6.8
* **Platform:** linux-rhel7-broadwell
* **Concretizer:** clingo


### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:
Right, I noticed this in #34891, but then reverted `spack mirror create` changes, cause they were breaking. Will push a fix soon.

Resolving patch:
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -9,6 +9,7 @@
 import llnl.util.tty as tty
 import llnl.util.tty.colify as colify
 
+import spack.caches
 import spack.cmd
 import spack.cmd.common.arguments as arguments
 import spack.concretize
@@ -18,7 +19,6 @@
 import spack.repo
 import spack.spec
 import spack.util.path
-import spack.util.url as url_util
 import spack.util.web as web_util
 from spack.error import SpackError
 from spack.util.spack_yaml import syaml_dict
@@ -374,11 +374,9 @@ def versions_per_spec(args):
     return num_versions
 
 
-def create_mirror_for_individual_specs(mirror_specs, directory_hint, skip_unstable_versions):
-    present, mirrored, error = spack.mirror.create(
-        directory_hint, mirror_specs, skip_unstable_versions
-    )
-    tty.msg("Summary for mirror in {}".format(directory_hint))
+def create_mirror_for_individual_specs(mirror_specs, path, skip_unstable_versions):
+    present, mirrored, error = spack.mirror.create(path, mirror_specs, skip_unstable_versions)
+    tty.msg("Summary for mirror in {}".format(path))
     process_mirror_stats(present, mirrored, error)
 
 
@@ -396,19 +394,6 @@ def process_mirror_stats(present, mirrored, error):
         sys.exit(1)
 
 
-def local_mirror_url_from_user(directory_hint):
-    """Return a file:// url pointing to the local mirror to be used.
-
-    Args:
-        directory_hint (str or None): directory where to create the mirror. If None,
-            defaults to "config:source_cache".
-    """
-    mirror_directory = spack.util.path.canonicalize_path(
-        directory_hint or spack.config.get("config:source_cache")
-    )
-    return url_util.path_to_file_url(mirror_directory)
-
-
 def mirror_create(args):
     """Create a directory to be used as a spack mirror, and fill it with
     package archives.
@@ -439,9 +424,12 @@ def mirror_create(args):
             "The option '--all' already implies mirroring all versions for each package.",
         )
 
+    # When no directory is provided, the source dir is used
+    path = args.directory or spack.caches.fetch_cache_location()
+
     if args.all and not ev.active_environment():
         create_mirror_for_all_specs(
-            directory_hint=args.directory,
+            path=path,
             skip_unstable_versions=args.skip_unstable_versions,
             selection_fn=not_excluded_fn(args),
         )
@@ -449,7 +437,7 @@ def mirror_create(args):
 
     if args.all and ev.active_environment():
         create_mirror_for_all_specs_inside_environment(
-            directory_hint=args.directory,
+            path=path,
             skip_unstable_versions=args.skip_unstable_versions,
             selection_fn=not_excluded_fn(args),
         )
@@ -458,16 +446,15 @@ def mirror_create(args):
     mirror_specs = concrete_specs_from_user(args)
     create_mirror_for_individual_specs(
         mirror_specs,
-        directory_hint=args.directory,
+        path=path,
         skip_unstable_versions=args.skip_unstable_versions,
     )
 
 
-def create_mirror_for_all_specs(directory_hint, skip_unstable_versions, selection_fn):
+def create_mirror_for_all_specs(path, skip_unstable_versions, selection_fn):
     mirror_specs = all_specs_with_all_versions(selection_fn=selection_fn)
-    local_push_url = local_mirror_url_from_user(directory_hint=directory_hint)
     mirror_cache, mirror_stats = spack.mirror.mirror_cache_and_stats(
-        local_push_url, skip_unstable_versions=skip_unstable_versions
+        path, skip_unstable_versions=skip_unstable_versions
     )
     for candidate in mirror_specs:
         pkg_cls = spack.repo.path.get_pkg_class(candidate.name)
@@ -477,13 +464,11 @@ def create_mirror_for_all_specs(directory_hint, skip_unstable_versions, selectio
     process_mirror_stats(*mirror_stats.stats())
 
 
-def create_mirror_for_all_specs_inside_environment(
-    directory_hint, skip_unstable_versions, selection_fn
-):
+def create_mirror_for_all_specs_inside_environment(path, skip_unstable_versions, selection_fn):
     mirror_specs = concrete_specs_from_environment(selection_fn=selection_fn)
     create_mirror_for_individual_specs(
         mirror_specs,
-        directory_hint=directory_hint,
+        path=path,
         skip_unstable_versions=skip_unstable_versions,
     )
 


Test patch:
diff --git a/lib/spack/spack/test/cmd/mirror.py b/lib/spack/spack/test/cmd/mirror.py
--- a/lib/spack/spack/test/cmd/mirror.py
+++ b/lib/spack/spack/test/cmd/mirror.py
@@ -301,20 +301,6 @@ def test_error_conditions(self, cli_args, error_str):
         with pytest.raises(spack.error.SpackError, match=error_str):
             spack.cmd.mirror.mirror_create(args)
 
-    @pytest.mark.parametrize(
-        "cli_args,expected_end",
-        [
-            ({"directory": None}, os.path.join("source")),
-            ({"directory": os.path.join("foo", "bar")}, os.path.join("foo", "bar")),
-        ],
-    )
-    def test_mirror_path_is_valid(self, cli_args, expected_end, config):
-        args = MockMirrorArgs(**cli_args)
-        local_push_url = spack.cmd.mirror.local_mirror_url_from_user(args.directory)
-        assert local_push_url.startswith("file:")
-        assert os.path.isabs(local_push_url.replace("file://", ""))
-        assert local_push_url.endswith(expected_end)
-
     @pytest.mark.parametrize(
         "cli_args,not_expected",
         [