environments: don't replace relative view path with absolute path on concretize/install (#34958)

* environments: don't rewrite relative view path, expand path on cli ahead of time

Currently if you have a spack.yaml that specifies a view by relative
path, Spack expands it to an absolute path on `spack -e . install` and
persists that to disk.

This is rather annoying when you have a `spack.yaml` file inside a git
repo, cause you want to use relative paths to make it relocatable, but
you constantly have to undo the changes made to spack.yaml by Spack.

So, as an alternative:

1. Always stick to paths as they are provided in spack.yaml, never
   replace them with a canonicalized version
2. Turn relative paths on the command line into absolute paths before
   storing to spack.yaml. This way you can do `spack env create --dir
   ./env --with-view ./view` and both `./env` and `./view` are resolved
   to the current working dir, as expected (not `./env/view`). This
   corresponds to the old behavior of `spack env create`.

* create --with-view always takes a value
This commit is contained in:
Harmen Stoppels 2023-01-23 19:03:54 +01:00 committed by GitHub
parent b8684008d0
commit 13739e0783
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 4 deletions

View File

@ -291,7 +291,11 @@ def env_create_setup_parser(subparser):
def env_create(args): def env_create(args):
if args.with_view: if args.with_view:
with_view = args.with_view # Expand relative paths provided on the command line to the current working directory
# This way we interpret `spack env create --with-view ./view --dir ./env` as
# a view in $PWD/view, not $PWD/env/view. This is different from specifying a relative
# path in spack.yaml, which is resolved relative to the environment file.
with_view = os.path.abspath(args.with_view)
elif args.without_view: elif args.without_view:
with_view = False with_view = False
else: else:

View File

@ -384,7 +384,8 @@ def __init__(
link_type="symlink", link_type="symlink",
): ):
self.base = base_path self.base = base_path
self.root = spack.util.path.canonicalize_path(root) self.raw_root = root
self.root = spack.util.path.canonicalize_path(root, default_wd=base_path)
self.projections = projections self.projections = projections
self.select = select self.select = select
self.exclude = exclude self.exclude = exclude
@ -410,7 +411,7 @@ def __eq__(self, other):
) )
def to_dict(self): def to_dict(self):
ret = syaml.syaml_dict([("root", self.root)]) ret = syaml.syaml_dict([("root", self.raw_root)])
if self.projections: if self.projections:
# projections guaranteed to be ordered dict if true-ish # projections guaranteed to be ordered dict if true-ish
# for python2.6, may be syaml or ruamel.yaml implementation # for python2.6, may be syaml or ruamel.yaml implementation
@ -2127,7 +2128,7 @@ def _update_and_write_manifest(self, raw_yaml_dict, yaml_dict):
# Construct YAML representation of view # Construct YAML representation of view
default_name = default_view_name default_name = default_view_name
if self.views and len(self.views) == 1 and default_name in self.views: if self.views and len(self.views) == 1 and default_name in self.views:
path = self.default_view.root path = self.default_view.raw_root
if self.default_view == ViewDescriptor(self.path, self.view_path_default): if self.default_view == ViewDescriptor(self.path, self.view_path_default):
view = True view = True
elif self.default_view == ViewDescriptor(self.path, path): elif self.default_view == ViewDescriptor(self.path, path):

View File

@ -3229,3 +3229,10 @@ def test_env_include_packages_url(
cfg = spack.config.get("packages") cfg = spack.config.get("packages")
assert "openmpi" in cfg["all"]["providers"]["mpi"] assert "openmpi" in cfg["all"]["providers"]["mpi"]
def test_relative_view_path_on_command_line_is_made_absolute(tmpdir, config):
with fs.working_dir(str(tmpdir)):
env("create", "--with-view", "view", "--dir", "env")
environment = ev.Environment(os.path.join(".", "env"))
assert os.path.samefile("view", environment.default_view.root)

View File

@ -4,10 +4,13 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""Test environment internals without CLI""" """Test environment internals without CLI"""
import io import io
import os
import sys import sys
import pytest import pytest
import llnl.util.filesystem as fs
import spack.environment as ev import spack.environment as ev
import spack.spec import spack.spec
@ -111,3 +114,32 @@ def test_activate_should_require_an_env():
with pytest.raises(TypeError): with pytest.raises(TypeError):
ev.activate(env=None) ev.activate(env=None)
def test_user_view_path_is_not_canonicalized_in_yaml(tmpdir, config):
# When spack.yaml files are checked into version control, we
# don't want view: ./relative to get canonicalized on disk.
# We create a view in <tmpdir>/env_dir
env_path = tmpdir.mkdir("env_dir").strpath
# And use a relative path to specify the view dir
view = os.path.join(".", "view")
# Which should always resolve to the following independent of cwd.
absolute_view = os.path.join(env_path, "view")
# Serialize environment with relative view path
with fs.working_dir(str(tmpdir)):
fst = ev.Environment(env_path, with_view=view)
fst.write()
# The view link should be created
assert os.path.isdir(absolute_view)
# Deserialize and check if the view path is still relative in yaml
# and also check that the getter is pointing to the right dir.
with fs.working_dir(str(tmpdir)):
snd = ev.Environment(env_path)
assert snd.yaml["spack"]["view"] == view
assert os.path.samefile(snd.default_view.root, absolute_view)