Support windows paths in a spec (#34405)
Refactor Spack's spec syntax parsing to handle Windows style paths Amend unit tests to reflect this updated behavior.
This commit is contained in:
parent
6e95417c98
commit
ff38ff25cb
@ -59,6 +59,7 @@
|
|||||||
import enum
|
import enum
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from typing import Iterator, List, Match, Optional
|
from typing import Iterator, List, Match, Optional
|
||||||
|
|
||||||
from llnl.util.tty import color
|
from llnl.util.tty import color
|
||||||
@ -68,6 +69,7 @@
|
|||||||
import spack.variant
|
import spack.variant
|
||||||
import spack.version
|
import spack.version
|
||||||
|
|
||||||
|
IS_WINDOWS = sys.platform == "win32"
|
||||||
#: Valid name for specs and variants. Here we are not using
|
#: Valid name for specs and variants. Here we are not using
|
||||||
#: the previous "w[\w.-]*" since that would match most
|
#: the previous "w[\w.-]*" since that would match most
|
||||||
#: characters that can be part of a word in any language
|
#: characters that can be part of a word in any language
|
||||||
@ -80,8 +82,15 @@
|
|||||||
|
|
||||||
HASH = r"[a-zA-Z_0-9]+"
|
HASH = r"[a-zA-Z_0-9]+"
|
||||||
|
|
||||||
#: A filename starts either with a "." or a "/" or a "{name}/"
|
#: A filename starts either with a "." or a "/" or a "{name}/,
|
||||||
FILENAME = r"(\.|\/|[a-zA-Z0-9-_]*\/)([a-zA-Z0-9-_\.\/]*)(\.json|\.yaml)"
|
# or on Windows, a drive letter followed by a colon and "\"
|
||||||
|
# or "." or {name}\
|
||||||
|
WINDOWS_FILENAME = r"(\.|[a-zA-Z0-9-_]*\\|[a-zA-Z]:\\)([a-zA-Z0-9-_\.\\]*)(\.json|\.yaml)"
|
||||||
|
UNIX_FILENAME = r"(\.|\/|[a-zA-Z0-9-_]*\/)([a-zA-Z0-9-_\.\/]*)(\.json|\.yaml)"
|
||||||
|
if not IS_WINDOWS:
|
||||||
|
FILENAME = UNIX_FILENAME
|
||||||
|
else:
|
||||||
|
FILENAME = WINDOWS_FILENAME
|
||||||
|
|
||||||
VALUE = r"([a-zA-Z_0-9\-+\*.,:=\~\/\\]+)"
|
VALUE = r"([a-zA-Z_0-9\-+\*.,:=\~\/\\]+)"
|
||||||
QUOTED_VALUE = r"[\"']+([a-zA-Z_0-9\-+\*.,:=\~\/\\\s]+)[\"']+"
|
QUOTED_VALUE = r"[\"']+([a-zA-Z_0-9\-+\*.,:=\~\/\\\s]+)[\"']+"
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
import itertools
|
import itertools
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -10,9 +12,24 @@
|
|||||||
import spack.platforms.test
|
import spack.platforms.test
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.variant
|
import spack.variant
|
||||||
from spack.parser import SpecParser, SpecTokenizationError, Token, TokenType
|
from spack.parser import (
|
||||||
|
UNIX_FILENAME,
|
||||||
|
WINDOWS_FILENAME,
|
||||||
|
SpecParser,
|
||||||
|
SpecTokenizationError,
|
||||||
|
Token,
|
||||||
|
TokenType,
|
||||||
|
)
|
||||||
|
|
||||||
is_windows = sys.platform == "win32"
|
FAIL_ON_WINDOWS = pytest.mark.xfail(
|
||||||
|
sys.platform == "win32",
|
||||||
|
raises=(SpecTokenizationError, spack.spec.NoSuchHashError),
|
||||||
|
reason="Unix style path on Windows",
|
||||||
|
)
|
||||||
|
|
||||||
|
FAIL_ON_UNIX = pytest.mark.xfail(
|
||||||
|
sys.platform != "win32", raises=SpecTokenizationError, reason="Windows style path on Unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def simple_package_name(name):
|
def simple_package_name(name):
|
||||||
@ -818,18 +835,6 @@ def test_redundant_spec(query_str, text_fmt, database):
|
|||||||
("x platform=test platform=test", spack.spec.DuplicateArchitectureError),
|
("x platform=test platform=test", spack.spec.DuplicateArchitectureError),
|
||||||
("x os=fe platform=test target=fe os=fe", spack.spec.DuplicateArchitectureError),
|
("x os=fe platform=test target=fe os=fe", spack.spec.DuplicateArchitectureError),
|
||||||
("x target=be platform=test os=be os=fe", spack.spec.DuplicateArchitectureError),
|
("x target=be platform=test os=be os=fe", spack.spec.DuplicateArchitectureError),
|
||||||
# Specfile related errors
|
|
||||||
("/bogus/path/libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("../../libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("./libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("libfoo ^/bogus/path/libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("libfoo ^../../libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("libfoo ^./libdwarf.yaml", spack.spec.NoSuchSpecFileError),
|
|
||||||
("/bogus/path/libdwarf.yamlfoobar", spack.spec.SpecFilenameError),
|
|
||||||
(
|
|
||||||
"libdwarf^/bogus/path/libelf.yamlfoobar ^/path/to/bogus.yaml",
|
|
||||||
spack.spec.SpecFilenameError,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_error_conditions(text, exc_cls):
|
def test_error_conditions(text, exc_cls):
|
||||||
@ -837,7 +842,114 @@ def test_error_conditions(text, exc_cls):
|
|||||||
SpecParser(text).next_spec()
|
SpecParser(text).next_spec()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_windows, reason="Spec parsing does not currently support Windows paths")
|
@pytest.mark.parametrize(
|
||||||
|
"text,exc_cls",
|
||||||
|
[
|
||||||
|
# Specfile related errors
|
||||||
|
pytest.param(
|
||||||
|
"/bogus/path/libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"../../libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"./libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^/bogus/path/libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^../../libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^./libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"/bogus/path/libdwarf.yamlfoobar",
|
||||||
|
spack.spec.SpecFilenameError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libdwarf^/bogus/path/libelf.yamlfoobar ^/path/to/bogus.yaml",
|
||||||
|
spack.spec.SpecFilenameError,
|
||||||
|
marks=FAIL_ON_WINDOWS,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"c:\\bogus\\path\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"..\\..\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
".\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^c:\\bogus\\path\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^..\\..\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libfoo ^.\\libdwarf.yaml",
|
||||||
|
spack.spec.NoSuchSpecFileError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"c:\\bogus\\path\\libdwarf.yamlfoobar",
|
||||||
|
spack.spec.SpecFilenameError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"libdwarf^c:\\bogus\\path\\libelf.yamlfoobar ^c:\\path\\to\\bogus.yaml",
|
||||||
|
spack.spec.SpecFilenameError,
|
||||||
|
marks=FAIL_ON_UNIX,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_specfile_error_conditions_windows(text, exc_cls):
|
||||||
|
with pytest.raises(exc_cls):
|
||||||
|
SpecParser(text).next_spec()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"filename,regex",
|
||||||
|
[
|
||||||
|
(r"c:\abs\windows\\path.yaml", WINDOWS_FILENAME),
|
||||||
|
(r".\\relative\\dot\\win\\path.yaml", WINDOWS_FILENAME),
|
||||||
|
(r"relative\\windows\\path.yaml", WINDOWS_FILENAME),
|
||||||
|
("/absolute/path/to/file.yaml", UNIX_FILENAME),
|
||||||
|
("relative/path/to/file.yaml", UNIX_FILENAME),
|
||||||
|
("./dot/rel/to/file.yaml", UNIX_FILENAME),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_specfile_parsing(filename, regex):
|
||||||
|
match = re.match(regex, filename)
|
||||||
|
assert match
|
||||||
|
assert match.end() == len(filename)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_specfile_simple(specfile_for, tmpdir):
|
def test_parse_specfile_simple(specfile_for, tmpdir):
|
||||||
specfile = tmpdir.join("libdwarf.json")
|
specfile = tmpdir.join("libdwarf.json")
|
||||||
s = specfile_for("libdwarf", specfile)
|
s = specfile_for("libdwarf", specfile)
|
||||||
@ -883,7 +995,6 @@ def test_parse_filename_missing_slash_as_spec(specfile_for, tmpdir, filename):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_windows, reason="Spec parsing does not currently support Windows paths")
|
|
||||||
def test_parse_specfile_dependency(default_mock_concretization, tmpdir):
|
def test_parse_specfile_dependency(default_mock_concretization, tmpdir):
|
||||||
"""Ensure we can use a specfile as a dependency"""
|
"""Ensure we can use a specfile as a dependency"""
|
||||||
s = default_mock_concretization("libdwarf")
|
s = default_mock_concretization("libdwarf")
|
||||||
@ -899,12 +1010,13 @@ def test_parse_specfile_dependency(default_mock_concretization, tmpdir):
|
|||||||
|
|
||||||
with specfile.dirpath().as_cwd():
|
with specfile.dirpath().as_cwd():
|
||||||
# Make sure this also works: "spack spec ./libelf.yaml"
|
# Make sure this also works: "spack spec ./libelf.yaml"
|
||||||
spec = SpecParser(f"libdwarf^./{specfile.basename}").next_spec()
|
spec = SpecParser(f"libdwarf^.{os.path.sep}{specfile.basename}").next_spec()
|
||||||
assert spec["libelf"] == s["libelf"]
|
assert spec["libelf"] == s["libelf"]
|
||||||
|
|
||||||
# Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml"
|
# Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml"
|
||||||
spec = SpecParser(
|
spec = SpecParser(
|
||||||
f"libdwarf^../{specfile.dirpath().basename}/{specfile.basename}"
|
f"libdwarf^..{os.path.sep}{specfile.dirpath().basename}\
|
||||||
|
{os.path.sep}{specfile.basename}"
|
||||||
).next_spec()
|
).next_spec()
|
||||||
assert spec["libelf"] == s["libelf"]
|
assert spec["libelf"] == s["libelf"]
|
||||||
|
|
||||||
@ -918,16 +1030,20 @@ def test_parse_specfile_relative_paths(specfile_for, tmpdir):
|
|||||||
|
|
||||||
with parent_dir.as_cwd():
|
with parent_dir.as_cwd():
|
||||||
# Make sure this also works: "spack spec ./libelf.yaml"
|
# Make sure this also works: "spack spec ./libelf.yaml"
|
||||||
spec = SpecParser(f"./{basename}").next_spec()
|
spec = SpecParser(f".{os.path.sep}{basename}").next_spec()
|
||||||
assert spec == s
|
assert spec == s
|
||||||
|
|
||||||
# Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml"
|
# Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml"
|
||||||
spec = SpecParser(f"../{parent_dir.basename}/{basename}").next_spec()
|
spec = SpecParser(
|
||||||
|
f"..{os.path.sep}{parent_dir.basename}{os.path.sep}{basename}"
|
||||||
|
).next_spec()
|
||||||
assert spec == s
|
assert spec == s
|
||||||
|
|
||||||
# Should also handle mixed clispecs and relative paths, e.g.:
|
# Should also handle mixed clispecs and relative paths, e.g.:
|
||||||
# "spack spec mvapich_foo ../<cur-dir>/libelf.yaml"
|
# "spack spec mvapich_foo ../<cur-dir>/libelf.yaml"
|
||||||
specs = SpecParser(f"mvapich_foo ../{parent_dir.basename}/{basename}").all_specs()
|
specs = SpecParser(
|
||||||
|
f"mvapich_foo ..{os.path.sep}{parent_dir.basename}{os.path.sep}{basename}"
|
||||||
|
).all_specs()
|
||||||
assert len(specs) == 2
|
assert len(specs) == 2
|
||||||
assert specs[1] == s
|
assert specs[1] == s
|
||||||
|
|
||||||
@ -937,7 +1053,7 @@ def test_parse_specfile_relative_subdir_path(specfile_for, tmpdir):
|
|||||||
s = specfile_for("libdwarf", specfile)
|
s = specfile_for("libdwarf", specfile)
|
||||||
|
|
||||||
with tmpdir.as_cwd():
|
with tmpdir.as_cwd():
|
||||||
spec = SpecParser(f"subdir/{specfile.basename}").next_spec()
|
spec = SpecParser(f"subdir{os.path.sep}{specfile.basename}").next_spec()
|
||||||
assert spec == s
|
assert spec == s
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user