Skip to content

Commit 788ba64

Browse files
When chaining against chained envs, get upstreams+envrepo's (#1396)
This PR makes the env chaining logic in spack stack create env --upstream more robust, specifically by recursively checking to see whether the upstream environments are themselves chained environments and adding those to the new environment's list of upstreams. It also automatically populates an envrepo/ repo directory from those found in the upstream environments.
1 parent 83d2c1f commit 788ba64

File tree

3 files changed

+74
-11
lines changed

3 files changed

+74
-11
lines changed

.github/workflows/ubuntu-ci-x86_64-gnu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ jobs:
196196
echo "# nothing" >> ${SPACK_STACK_DIR}/envs/chaintest/common/packages.yaml
197197
spack stack create env --name chaintest3 --site linux.default --compiler gcc --upstream ${SPACK_STACK_DIR}/envs/chaintest/install 2>&1 | tee stderr.txt
198198
cnt=$(grep -c "WARNING.*do not match" stderr.txt || true)
199-
if [ $cnt -ne 2 ]; then echo "Missing 'create env' warnings"; exit 1; fi
199+
if [ $cnt -lt 3 ]; then echo "Missing 'create env' warnings"; exit 1; fi
200200
201201
- name: test-env
202202
run: |

spack-ext/lib/jcsda-emc/spack-stack/stack/stack_env.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,16 @@ def _copy_site_includes(self):
207207
destination = os.path.join(env_site_dir, "packages.yaml")
208208
self._copy_or_merge_includes("packages", packages_yaml_path, packages_compiler_yaml_path, destination)
209209

210+
def get_upstream_realpaths(self, upstream_path):
211+
spack_yaml_path = os.path.realpath(os.path.join(upstream_path, "../spack.yaml"))
212+
with open(spack_yaml_path, "r") as f:
213+
spack_yaml = syaml.load_config(f)
214+
upstream_paths = [upstream_path]
215+
if "upstreams" in spack_yaml["spack"].keys():
216+
entries = spack_yaml["spack"]["upstreams"]
217+
for entry in entries.items():
218+
upstream_paths += self.get_upstream_realpaths(entry[1]["install_tree"])
219+
return upstream_paths
210220

211221
def write(self):
212222
"""Write environment out to a spack.yaml in <env_dir>/<name>.
@@ -280,10 +290,10 @@ def write(self):
280290
"common": os.path.join(self.env_dir(), "common"),
281291
"site": os.path.join(self.env_dir(), "site"),
282292
}
283-
for upstream_path in self.upstreams:
284-
upstream_path = upstream_path[0]
285-
# spack doesn't handle "~/" correctly, this fixes it:
286-
upstream_path = os.path.expanduser(upstream_path)
293+
all_upstreams = []
294+
for upstream in self.upstreams:
295+
all_upstreams.extend(self.get_upstream_realpaths(upstream[0]))
296+
for upstream_path in all_upstreams:
287297
if not os.path.basename(os.path.normpath(upstream_path)) == "install":
288298
logging.warning(
289299
"WARNING: Upstream path '%s' is not an 'install' directory!"
@@ -298,7 +308,7 @@ def write(self):
298308
if path_parts:
299309
name = path_parts["spack_stack_ver"] + "-" + path_parts["env_name"]
300310
else:
301-
name = os.path.basename(upstream_path)
311+
name = os.path.realpath(os.path.join(upstream_path, ".."))
302312
upstream = "upstreams:%s:install_tree:'%s'" % (name, upstream_path)
303313
logging.info("Adding upstream path '%s'" % upstream_path)
304314
spack.config.add(upstream, scope=env_scope)
@@ -319,12 +329,20 @@ def write(self):
319329
f"'{upstream_path}' do not match! Verify that you are using the same "
320330
"version of spack-stack, or really, really know what you are doing.")
321331
)
332+
new_envrepo = os.path.join(self.env_dir(), "envrepo")
333+
for upstream_path in all_upstreams[::-1]:
334+
envrepo_path = os.path.realpath(os.path.join(upstream_path, "../envrepo"))
335+
if os.path.isdir(envrepo_path):
336+
shutil.copytree(envrepo_path, new_envrepo, dirs_exist_ok=True)
337+
if os.path.isdir(new_envrepo):
338+
repo_cfg = "repos:[$env/envrepo]"
339+
spack.config.add(repo_cfg, scope=env_scope)
322340

323341
if self.modifypkg:
324342
logging.info("Creating custom repo with packages %s" % ", ".join(self.modifypkg))
325343
env_repo_path = os.path.join(env_dir, "envrepo")
326344
env_pkgs_path = os.path.join(env_repo_path, "packages")
327-
os.makedirs(env_pkgs_path, exist_ok=False)
345+
os.makedirs(env_pkgs_path, exist_ok=True)
328346
with open(os.path.join(env_repo_path, "repo.yaml"), "w") as f:
329347
f.write("repo:\n namespace: envrepo")
330348
repo_paths = spack.config.get("repos")
@@ -338,6 +356,7 @@ def write(self):
338356
pkg_path,
339357
os.path.join(env_pkgs_path, pkg_name),
340358
ignore=shutil.ignore_patterns("__pycache__"),
359+
dirs_exist_ok=True,
341360
)
342361
pkg_found = True
343362
# Use the first repo where the package exists:

spack-ext/lib/jcsda-emc/spack-stack/tests/test_stack_create.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,29 +179,73 @@ def test_compilers():
179179
@pytest.mark.extension("stack")
180180
@pytest.mark.filterwarnings("ignore::UserWarning")
181181
def test_upstream():
182+
base_env = os.path.join(test_dir, "base_env/install/")
183+
os.makedirs(os.path.join(base_env, ".spack-db/"), exist_ok=True)
184+
base_env_spack_yaml_path = os.path.realpath(os.path.join(base_env, "../spack.yaml"))
185+
f_base_env = open(base_env_spack_yaml_path, "w")
186+
f_base_env.write("spack:\n dummytag: dummyvalue")
187+
f_base_env.close()
182188
stack_create(
183189
"create",
184190
"env",
185191
"--site",
186192
"hera",
187193
"--name",
188-
"upstream_test",
194+
"chainedA",
189195
"--dir",
190196
test_dir,
191197
"--compiler",
192198
"gcc",
193199
"--upstream",
194-
"/test/path/to/upstream/env",
200+
base_env,
195201
)
196-
spack_yaml_path = os.path.join(test_dir, "upstream_test", "spack.yaml")
202+
spack_yaml_path = os.path.join(test_dir, "chainedA", "spack.yaml")
197203
with open(spack_yaml_path, "r") as f:
198204
spack_yaml_txt = f.read()
199-
assert "install_tree: /test/path/to/upstream/env" in spack_yaml_txt
205+
assert f"install_tree: {base_env}" in spack_yaml_txt
200206
assert (
201207
"repos: [$env/envrepo]" not in spack_yaml_txt
202208
), "--modify-pkg functionality modified spack.yaml without being called"
203209

204210

211+
@pytest.mark.extension("stack")
212+
@pytest.mark.filterwarnings("ignore::UserWarning")
213+
def test_layered_upstreams():
214+
os.makedirs(os.path.join(test_dir, "chainedA/install/.spack-db/"))
215+
os.makedirs(os.path.join(test_dir, "base_env/envrepo/"))
216+
os.makedirs(os.path.join(test_dir, "chainedA/envrepo/"))
217+
f_base_env_envrepo_file_path = os.path.join(test_dir, "base_env/envrepo/file")
218+
f_chainedA_envrepo_file_path = os.path.join(test_dir, "chainedA/envrepo/file")
219+
f_base_env_envrepo_file = open(f_base_env_envrepo_file_path, "w")
220+
f_chainedA_envrepo_file = open(f_chainedA_envrepo_file_path, "w")
221+
f_base_env_envrepo_file.write("bad")
222+
f_chainedA_envrepo_file.write("good")
223+
f_base_env_envrepo_file.close()
224+
f_chainedA_envrepo_file.close()
225+
stack_create(
226+
"create",
227+
"env",
228+
"--site",
229+
"hera",
230+
"--name",
231+
"chainedB",
232+
"--dir",
233+
test_dir,
234+
"--compiler",
235+
"gcc",
236+
"--upstream",
237+
os.path.join(test_dir, "chainedA/install/")
238+
)
239+
spack_yaml_path = os.path.join(test_dir, "chainedB", "spack.yaml")
240+
with open(spack_yaml_path, "r") as f:
241+
spack_yaml_txt = f.read()
242+
assert "/base_env/install/" in spack_yaml_txt
243+
assert "/chainedA/install/" in spack_yaml_txt
244+
file_path = os.path.join(test_dir, "chainedB/envrepo/file")
245+
with open(file_path, "r") as f:
246+
assert "good" in f.read()
247+
248+
205249
@pytest.mark.extension("stack")
206250
@pytest.mark.filterwarnings("ignore::UserWarning")
207251
def test_modifypkg():

0 commit comments

Comments
 (0)