Repository: spack/spack

Issue:
Feature Request: Create Mirror for Environment
Need the ability to generate a mirror for an entire environment in support of supplying a package mirror to non-internet connected systems at LANL



Discussion:

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
@@ -17,6 +17,7 @@
 import spack.mirror
 import spack.repo
 import spack.cmd.common.arguments as arguments
+import spack.environment as ev
 from spack.spec import Spec
 from spack.error import SpackError
 from spack.util.spack_yaml import syaml_dict
@@ -161,10 +162,14 @@ def mirror_create(args):
                 tty.die("Cannot pass specs on the command line with --file.")
             specs = _read_specs_from_file(args.file)
 
-        # If nothing is passed, use all packages.
+        # If nothing is passed, use environment or all if no active env
         if not specs:
-            specs = [Spec(n) for n in spack.repo.all_package_names()]
-            specs.sort(key=lambda s: s.format("{name}{@version}").lower())
+            env = ev.get_env(args, 'mirror')
+            if env:
+                specs = env.specs_by_hash.values()
+            else:
+                specs = [Spec(n) for n in spack.repo.all_package_names()]
+                specs.sort(key=lambda s: s.format("{name}{@version}").lower())
 
         # If the user asked for dependencies, traverse spec DAG get them.
         if args.dependencies:
diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py
--- a/lib/spack/spack/mirror.py
+++ b/lib/spack/spack/mirror.py
@@ -104,7 +104,8 @@ def get_matching_versions(specs, **kwargs):
         if spec.concrete:
             matching_spec.append(spec)
             pkg_versions -= 1
-            version_order.remove(spec.version)
+            if spec.version in version_order:
+                version_order.remove(spec.version)
 
         for v in version_order:
             # Generate no more than num_versions versions for each spec.


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
@@ -4,10 +4,16 @@
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
 import pytest
+import os
 
 from spack.main import SpackCommand
+import spack.environment as ev
+import spack.config
 
 mirror = SpackCommand('mirror')
+env = SpackCommand('env')
+add = SpackCommand('add')
+concretize = SpackCommand('concretize')
 
 
 @pytest.mark.disable_clean_stage_check
@@ -17,3 +23,25 @@ def test_regression_8083(tmpdir, capfd, mock_packages, mock_fetch, config):
         output = mirror('create', '-d', str(tmpdir), 'externaltool')
     assert 'Skipping' in output
     assert 'as it is an external spec' in output
+
+
+@pytest.mark.regression('12345')
+def test_mirror_from_env(tmpdir, mock_packages, mock_fetch, config,
+                         mutable_mock_env_path):
+    mirror_dir = str(tmpdir)
+    env_name = 'test'
+
+    env('create', env_name)
+    with ev.read(env_name):
+        add('trivial-install-test-package')
+        add('git-test')
+        concretize()
+        with spack.config.override('config:checksum', False):
+            mirror('create', '-d', mirror_dir)
+
+    e = ev.read(env_name)
+    assert set(os.listdir(mirror_dir)) == set([s.name for s in e.user_specs])
+    for spec in e.specs_by_hash.values():
+        mirror_res = os.listdir(os.path.join(mirror_dir, spec.name))
+        expected = ['%s.tar.gz' % spec.format('{name}-{version}')]
+        assert mirror_res == expected