Partial removal of circular dependencies between spack and llnl (#40090)

Modifications:
- [x] Move `spack.util.string` to `llnl.string`
- [x] Remove dependency of `llnl` on `spack.error`
- [x] Move path of `spack.util.path` to `llnl.path`
- [x] Move `spack.util.environment.get_host_*` to `spack.spec`
This commit is contained in:
Massimiliano Culpo 2023-09-28 18:21:52 +02:00 committed by GitHub
parent f77a38a96b
commit a236fce31f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 297 additions and 258 deletions

105
lib/spack/llnl/path.py Normal file
View File

@ -0,0 +1,105 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""Path primitives that just require Python standard library."""
import functools
import sys
from typing import List, Optional
from urllib.parse import urlparse
class Path:
"""Enum to identify the path-style."""
unix: int = 0
windows: int = 1
platform_path: int = windows if sys.platform == "win32" else unix
def format_os_path(path: str, mode: int = Path.unix) -> str:
"""Formats the input path to use consistent, platform specific separators.
Absolute paths are converted between drive letters and a prepended '/' as per platform
requirement.
Parameters:
path: the path to be normalized, must be a string or expose the replace method.
mode: the path file separator style to normalize the passed path to.
Default is unix style, i.e. '/'
"""
if not path:
return path
if mode == Path.windows:
path = path.replace("/", "\\")
else:
path = path.replace("\\", "/")
return path
def convert_to_posix_path(path: str) -> str:
"""Converts the input path to POSIX style."""
return format_os_path(path, mode=Path.unix)
def convert_to_windows_path(path: str) -> str:
"""Converts the input path to Windows style."""
return format_os_path(path, mode=Path.windows)
def convert_to_platform_path(path: str) -> str:
"""Converts the input path to the current platform's native style."""
return format_os_path(path, mode=Path.platform_path)
def path_to_os_path(*parameters: str) -> List[str]:
"""Takes an arbitrary number of positional parameters, converts each argument of type
string to use a normalized filepath separator, and returns a list of all values.
"""
def _is_url(path_or_url: str) -> bool:
if "\\" in path_or_url:
return False
url_tuple = urlparse(path_or_url)
return bool(url_tuple.scheme) and len(url_tuple.scheme) > 1
result = []
for item in parameters:
if isinstance(item, str) and not _is_url(item):
item = convert_to_platform_path(item)
result.append(item)
return result
def system_path_filter(_func=None, arg_slice: Optional[slice] = None):
"""Filters function arguments to account for platform path separators.
Optional slicing range can be specified to select specific arguments
This decorator takes all (or a slice) of a method's positional arguments
and normalizes usage of filepath separators on a per platform basis.
Note: `**kwargs`, urls, and any type that is not a string are ignored
so in such cases where path normalization is required, that should be
handled by calling path_to_os_path directly as needed.
Parameters:
arg_slice: a slice object specifying the slice of arguments
in the decorated method over which filepath separators are
normalized
"""
def holder_func(func):
@functools.wraps(func)
def path_filter_caller(*args, **kwargs):
args = list(args)
if arg_slice:
args[arg_slice] = path_to_os_path(*args[arg_slice])
else:
args = path_to_os_path(*args)
return func(*args, **kwargs)
return path_filter_caller
if _func:
return holder_func(_func)
return holder_func

67
lib/spack/llnl/string.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""String manipulation functions that do not have other dependencies than Python
standard library
"""
from typing import List, Optional
def comma_list(sequence: List[str], article: str = "") -> str:
if type(sequence) is not list:
sequence = list(sequence)
if not sequence:
return ""
if len(sequence) == 1:
return sequence[0]
out = ", ".join(str(s) for s in sequence[:-1])
if len(sequence) != 2:
out += "," # oxford comma
out += " "
if article:
out += article + " "
out += str(sequence[-1])
return out
def comma_or(sequence: List[str]) -> str:
"""Return a string with all the elements of the input joined by comma, but the last
one (which is joined by 'or').
"""
return comma_list(sequence, "or")
def comma_and(sequence: List[str]) -> str:
"""Return a string with all the elements of the input joined by comma, but the last
one (which is joined by 'and').
"""
return comma_list(sequence, "and")
def quote(sequence: List[str], q: str = "'") -> List[str]:
"""Quotes each item in the input list with the quote character passed as second argument."""
return [f"{q}{e}{q}" for e in sequence]
def plural(n: int, singular: str, plural: Optional[str] = None, show_n: bool = True) -> str:
"""Pluralize <singular> word by adding an s if n != 1.
Arguments:
n: number of things there are
singular: singular form of word
plural: optional plural form, for when it's not just singular + 's'
show_n: whether to include n in the result string (default True)
Returns:
"1 thing" if n == 1 or "n things" if n != 1
"""
number = f"{n} " if show_n else ""
if n == 1:
return f"{number}{singular}"
elif plural is not None:
return f"{number}{plural}"
else:
return f"{number}{singular}s"

View File

@ -28,7 +28,8 @@
from llnl.util.symlink import islink, readlink, resolve_link_target_relative_to_the_link, symlink
from spack.util.executable import Executable, which
from spack.util.path import path_to_os_path, system_path_filter
from ..path import path_to_os_path, system_path_filter
if sys.platform != "win32":
import grp
@ -336,8 +337,7 @@ def groupid_to_group(x):
if string:
regex = re.escape(regex)
filenames = path_to_os_path(*filenames)
for filename in filenames:
for filename in path_to_os_path(*filenames):
msg = 'FILTER FILE: {0} [replacing "{1}"]'
tty.debug(msg.format(filename, regex))

View File

@ -14,7 +14,7 @@
from llnl.util import lang, tty
import spack.util.string
from ..string import plural
if sys.platform != "win32":
import fcntl
@ -169,7 +169,7 @@ def _attempts_str(wait_time, nattempts):
if nattempts <= 1:
return ""
attempts = spack.util.string.plural(nattempts, "attempt")
attempts = plural(nattempts, "attempt")
return " after {} and {}".format(lang.pretty_seconds(wait_time), attempts)

View File

@ -11,8 +11,7 @@
from llnl.util import lang, tty
from spack.error import SpackError
from spack.util.path import system_path_filter
from ..path import system_path_filter
if sys.platform == "win32":
from win32file import CreateHardLink
@ -338,7 +337,7 @@ def resolve_link_target_relative_to_the_link(link):
return os.path.join(link_dir, target)
class SymlinkError(SpackError):
class SymlinkError(RuntimeError):
"""Exception class for errors raised while creating symlinks,
junctions and hard links
"""

View File

@ -43,6 +43,7 @@
from typing import List, Tuple
import llnl.util.tty as tty
from llnl.string import plural
from llnl.util.filesystem import join_path
from llnl.util.lang import dedupe
from llnl.util.symlink import symlink
@ -82,7 +83,6 @@
from spack.util.executable import Executable
from spack.util.log_parse import make_log_context, parse_log_events
from spack.util.module_cmd import load_module, module, path_from_modules
from spack.util.string import plural
#
# This can be set by the user to globally disable parallel builds.

View File

@ -11,6 +11,7 @@
from textwrap import dedent
from typing import List, Match, Tuple
import llnl.string
import llnl.util.tty as tty
from llnl.util.filesystem import join_path
from llnl.util.lang import attr_setdefault, index_by
@ -29,7 +30,6 @@
import spack.user_environment as uenv
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
import spack.util.string
# cmd has a submodule called "list" so preserve the python list module
python_list = list
@ -516,7 +516,7 @@ def print_how_many_pkgs(specs, pkg_type=""):
category, e.g. if pkg_type is "installed" then the message
would be "3 installed packages"
"""
tty.msg("%s" % spack.util.string.plural(len(specs), pkg_type + " package"))
tty.msg("%s" % llnl.string.plural(len(specs), pkg_type + " package"))
def spack_is_git_repo():

View File

@ -13,6 +13,7 @@
import llnl.util.tty as tty
import llnl.util.tty.color as clr
from llnl.string import plural
from llnl.util.lang import elide_list
import spack.binary_distribution as bindist
@ -32,7 +33,6 @@
from spack.cmd import display_specs
from spack.spec import Spec, save_dependency_specfiles
from spack.stage import Stage
from spack.util.string import plural
description = "create, download and install binary packages"
section = "packaging"

View File

@ -9,6 +9,7 @@
import sys
import tempfile
import llnl.string as string
import llnl.util.filesystem as fs
import llnl.util.tty as tty
from llnl.util.tty.colify import colify
@ -28,7 +29,6 @@
import spack.schema.env
import spack.spec
import spack.tengine
import spack.util.string as string
from spack.util.environment import EnvironmentModifications
description = "manage virtual environments"

View File

@ -6,10 +6,11 @@
import posixpath
import sys
from llnl.path import convert_to_posix_path
import spack.paths
import spack.util.executable
from spack.spec import Spec
from spack.util.path import convert_to_posix_path
description = "generate Windows installer"
section = "admin"

View File

@ -5,6 +5,7 @@
import io
import sys
import llnl.string
import llnl.util.tty as tty
import llnl.util.tty.colify as colify
@ -24,7 +25,7 @@ def report_tags(category, tags):
if isatty:
num = len(tags)
fmt = "{0} package tag".format(category)
buffer.write("{0}:\n".format(spack.util.string.plural(num, fmt)))
buffer.write("{0}:\n".format(llnl.string.plural(num, fmt)))
if tags:
colify.colify(tags, output=buffer, tty=isatty, indent=4)

View File

@ -13,6 +13,7 @@
import tempfile
from typing import List, Optional, Sequence
import llnl.path
import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
@ -24,7 +25,6 @@
import spack.util.module_cmd
import spack.version
from spack.util.environment import filter_system_paths
from spack.util.path import system_path_filter
__all__ = ["Compiler"]
@ -160,7 +160,7 @@ def _parse_link_paths(string):
return implicit_link_dirs
@system_path_filter
@llnl.path.system_path_filter
def _parse_non_system_link_dirs(string: str) -> List[str]:
"""Parses link paths out of compiler debug output.

View File

@ -120,10 +120,8 @@ def write_host_environment(self, spec):
versioning. We use it in the case that an analysis later needs to
easily access this information.
"""
from spack.util.environment import get_host_environment_metadata
env_file = self.env_metadata_path(spec)
environ = get_host_environment_metadata()
environ = spack.spec.get_host_environment_metadata()
with open(env_file, "w") as fd:
sjson.dump(environ, fd)

View File

@ -404,7 +404,7 @@ def _write_yaml(data, str_or_file):
def _eval_conditional(string):
"""Evaluate conditional definitions using restricted variable scope."""
valid_variables = spack.util.environment.get_host_environment()
valid_variables = spack.spec.get_host_environment()
valid_variables.update({"re": re, "env": os.environ})
return eval(string, valid_variables)

View File

@ -35,6 +35,7 @@
import llnl.util
import llnl.util.filesystem as fs
import llnl.util.tty as tty
from llnl.string import comma_and, quote
from llnl.util.filesystem import get_single_file, mkdirp, temp_cwd, temp_rename, working_dir
from llnl.util.symlink import symlink
@ -49,7 +50,6 @@
import spack.version.git_ref_lookup
from spack.util.compression import decompressor_for
from spack.util.executable import CommandNotFoundError, which
from spack.util.string import comma_and, quote
#: List of all fetch strategies, created by FetchStrategy metaclass.
all_strategies = []

View File

@ -17,6 +17,7 @@
import llnl.util.filesystem as fs
import llnl.util.tty as tty
from llnl.string import plural
from llnl.util.lang import nullcontext
from llnl.util.tty.color import colorize
@ -26,7 +27,6 @@
from spack.installer import InstallError
from spack.spec import Spec
from spack.util.prefix import Prefix
from spack.util.string import plural
#: Stand-alone test failure info type
TestFailureType = Tuple[BaseException, str]

View File

@ -26,6 +26,7 @@
import uuid
from typing import Any, Dict, List, Union
import llnl.path
import llnl.util.filesystem as fs
import llnl.util.lang
import llnl.util.tty as tty
@ -563,7 +564,7 @@ def __init__(
self.checker = package_checker
self.packages_path = self.checker.packages_path
if sys.platform == "win32":
self.packages_path = spack.util.path.convert_to_posix_path(self.packages_path)
self.packages_path = llnl.path.convert_to_posix_path(self.packages_path)
self.namespace = namespace
self.indexers: Dict[str, Indexer] = {}

View File

@ -54,10 +54,14 @@
import io
import itertools
import os
import platform
import re
import socket
import warnings
from typing import Callable, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import llnl.path
import llnl.string
import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty
@ -82,11 +86,9 @@
import spack.util.executable
import spack.util.hash
import spack.util.module_cmd as md
import spack.util.path as pth
import spack.util.prefix
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
import spack.util.string
import spack.variant as vt
import spack.version as vn
import spack.version.git_ref_lookup
@ -1390,7 +1392,7 @@ def _format_module_list(modules):
@property
def external_path(self):
return pth.path_to_os_path(self._external_path)[0]
return llnl.path.path_to_os_path(self._external_path)[0]
@external_path.setter
def external_path(self, ext_path):
@ -1799,7 +1801,7 @@ def prefix(self):
@prefix.setter
def prefix(self, value):
self._prefix = spack.util.prefix.Prefix(pth.convert_to_platform_path(value))
self._prefix = spack.util.prefix.Prefix(llnl.path.convert_to_platform_path(value))
def spec_hash(self, hash):
"""Utility method for computing different types of Spec hashes.
@ -5148,6 +5150,43 @@ def save_dependency_specfiles(root: Spec, output_directory: str, dependencies: L
fd.write(spec.to_json(hash=ht.dag_hash))
def get_host_environment_metadata() -> Dict[str, str]:
"""Get the host environment, reduce to a subset that we can store in
the install directory, and add the spack version.
"""
import spack.main
environ = get_host_environment()
return {
"host_os": environ["os"],
"platform": environ["platform"],
"host_target": environ["target"],
"hostname": environ["hostname"],
"spack_version": spack.main.get_version(),
"kernel_version": platform.version(),
}
def get_host_environment() -> Dict[str, Any]:
"""Return a dictionary (lookup) with host information (not including the
os.environ).
"""
host_platform = spack.platforms.host()
host_target = host_platform.target("default_target")
host_os = host_platform.operating_system("default_os")
arch_fmt = "platform={0} os={1} target={2}"
arch_spec = Spec(arch_fmt.format(host_platform, host_os, host_target))
return {
"target": str(host_target),
"os": str(host_os),
"platform": str(host_platform),
"arch": arch_spec,
"architecture": arch_spec,
"arch_str": str(arch_spec),
"hostname": socket.gethostname(),
}
class SpecParseError(spack.error.SpecError):
"""Wrapper for ParseError for when we're parsing specs."""
@ -5208,7 +5247,7 @@ class InvalidDependencyError(spack.error.SpecError):
def __init__(self, pkg, deps):
self.invalid_deps = deps
super().__init__(
"Package {0} does not depend on {1}".format(pkg, spack.util.string.comma_or(deps))
"Package {0} does not depend on {1}".format(pkg, llnl.string.comma_or(deps))
)

View File

@ -14,6 +14,7 @@
import tempfile
from typing import Callable, Dict, Iterable, Optional
import llnl.string
import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.filesystem import (
@ -37,7 +38,6 @@
import spack.util.lock
import spack.util.path as sup
import spack.util.pattern as pattern
import spack.util.string
import spack.util.url as url_util
from spack.util.crypto import bit_length, prefix_bits
@ -897,7 +897,7 @@ def get_checksums_for_versions(
num_ver = len(sorted_versions)
tty.msg(
f"Found {spack.util.string.plural(num_ver, 'version')} of {package_name}:",
f"Found {llnl.string.plural(num_ver, 'version')} of {package_name}:",
"",
*llnl.util.lang.elide_list(
["{0:{1}} {2}".format(str(v), max_len, url_by_version[v]) for v in sorted_versions]

View File

@ -9,6 +9,7 @@
import pytest
from llnl.path import Path, convert_to_platform_path
from llnl.util.filesystem import HeaderList, LibraryList
import spack.build_environment
@ -21,7 +22,6 @@
from spack.util.cpus import determine_number_of_jobs
from spack.util.environment import EnvironmentModifications
from spack.util.executable import Executable
from spack.util.path import Path, convert_to_platform_path
def os_pathsep_join(path, *pths):

View File

@ -7,13 +7,14 @@
import pytest
from llnl.path import convert_to_posix_path
import spack.bootstrap
import spack.bootstrap.core
import spack.config
import spack.environment as ev
import spack.main
import spack.mirror
from spack.util.path import convert_to_posix_path
_bootstrap = spack.main.SpackCommand("bootstrap")

View File

@ -12,11 +12,12 @@
import pytest
from llnl.path import path_to_os_path
import spack.paths
import spack.repo
from spack.directory_layout import DirectoryLayout, InvalidDirectoryLayoutParametersError
from spack.spec import Spec
from spack.util.path import path_to_os_path
# number of packages to test (to reduce test time)
max_packages = 10

View File

@ -0,0 +1,43 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import pytest
import llnl.string
@pytest.mark.parametrize(
"arguments,expected",
[
((0, "thing"), "0 things"),
((1, "thing"), "1 thing"),
((2, "thing"), "2 things"),
((1, "thing", "wombats"), "1 thing"),
((2, "thing", "wombats"), "2 wombats"),
((2, "thing", "wombats", False), "wombats"),
],
)
def test_plural(arguments, expected):
assert llnl.string.plural(*arguments) == expected
@pytest.mark.parametrize(
"arguments,expected",
[((["one", "two"],), ["'one'", "'two'"]), ((["one", "two"], "^"), ["^one^", "^two^"])],
)
def test_quote(arguments, expected):
assert llnl.string.quote(*arguments) == expected
@pytest.mark.parametrize(
"input,expected_and,expected_or",
[
(["foo"], "foo", "foo"),
(["foo", "bar"], "foo and bar", "foo or bar"),
(["foo", "bar", "baz"], "foo, bar, and baz", "foo, bar, or baz"),
],
)
def test_comma_and_or(input, expected_and, expected_or):
assert llnl.string.comma_and(input) == expected_and
assert llnl.string.comma_or(input) == expected_or

View File

@ -1,14 +0,0 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack.util.string import plural
def test_plural():
assert plural(0, "thing") == "0 things"
assert plural(1, "thing") == "1 thing"
assert plural(2, "thing") == "2 things"
assert plural(1, "thing", "wombats") == "1 thing"
assert plural(2, "thing", "wombats") == "2 wombats"

View File

@ -31,12 +31,12 @@
import re
import llnl.url
from llnl.path import convert_to_posix_path
from llnl.util.tty.color import cescape, colorize
import spack.error
import spack.util.web
import spack.version
from spack.util.path import convert_to_posix_path
#
# Note: We call the input to most of these functions a "path" but the functions

View File

@ -10,21 +10,16 @@
import os
import os.path
import pickle
import platform
import re
import socket
import sys
from functools import wraps
from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, Union
from llnl.path import path_to_os_path, system_path_filter
from llnl.util import tty
from llnl.util.lang import dedupe
import spack.platforms
import spack.spec
from .executable import Executable, which
from .path import path_to_os_path, system_path_filter
if sys.platform == "win32":
SYSTEM_PATHS = [
@ -224,43 +219,6 @@ def pickle_environment(path: Path, environment: Optional[Dict[str, str]] = None)
pickle.dump(dict(environment if environment else os.environ), pickle_file, protocol=2)
def get_host_environment_metadata() -> Dict[str, str]:
"""Get the host environment, reduce to a subset that we can store in
the install directory, and add the spack version.
"""
import spack.main
environ = get_host_environment()
return {
"host_os": environ["os"],
"platform": environ["platform"],
"host_target": environ["target"],
"hostname": environ["hostname"],
"spack_version": spack.main.get_version(),
"kernel_version": platform.version(),
}
def get_host_environment() -> Dict[str, Any]:
"""Return a dictionary (lookup) with host information (not including the
os.environ).
"""
host_platform = spack.platforms.host()
host_target = host_platform.target("default_target")
host_os = host_platform.operating_system("default_os")
arch_fmt = "platform={0} os={1} target={2}"
arch_spec = spack.spec.Spec(arch_fmt.format(host_platform, host_os, host_target))
return {
"target": str(host_target),
"os": str(host_os),
"platform": str(host_platform),
"arch": arch_spec,
"architecture": arch_spec,
"arch_str": str(arch_spec),
"hostname": socket.gethostname(),
}
@contextlib.contextmanager
def set_env(**kwargs):
"""Temporarily sets and restores environment variables.

View File

@ -15,7 +15,6 @@
import sys
import tempfile
from datetime import date
from urllib.parse import urlparse
import llnl.util.tty as tty
from llnl.util.lang import memoized
@ -98,31 +97,10 @@ def replacements():
SPACK_PATH_PADDING_CHARS = "__spack_path_placeholder__"
def is_path_url(path):
if "\\" in path:
return False
url_tuple = urlparse(path)
return bool(url_tuple.scheme) and len(url_tuple.scheme) > 1
def win_exe_ext():
return ".exe"
def path_to_os_path(*pths):
"""
Takes an arbitrary number of positional parameters
converts each arguemnt of type string to use a normalized
filepath separator, and returns a list of all values
"""
ret_pths = []
for pth in pths:
if isinstance(pth, str) and not is_path_url(pth):
pth = convert_to_platform_path(pth)
ret_pths.append(pth)
return ret_pths
def sanitize_filename(filename: str) -> str:
"""
Replaces unsupported characters (for the host) in a filename with underscores.
@ -145,42 +123,6 @@ def sanitize_filename(filename: str) -> str:
return re.sub(r'[\x00-\x1F\x7F"*/:<>?\\|]', "_", filename)
def system_path_filter(_func=None, arg_slice=None):
"""
Filters function arguments to account for platform path separators.
Optional slicing range can be specified to select specific arguments
This decorator takes all (or a slice) of a method's positional arguments
and normalizes usage of filepath separators on a per platform basis.
Note: **kwargs, urls, and any type that is not a string are ignored
so in such cases where path normalization is required, that should be
handled by calling path_to_os_path directly as needed.
Parameters:
arg_slice (slice): a slice object specifying the slice of arguments
in the decorated method over which filepath separators are
normalized
"""
from functools import wraps
def holder_func(func):
@wraps(func)
def path_filter_caller(*args, **kwargs):
args = list(args)
if arg_slice:
args[arg_slice] = path_to_os_path(*args[arg_slice])
else:
args = path_to_os_path(*args)
return func(*args, **kwargs)
return path_filter_caller
if _func:
return holder_func(_func)
return holder_func
@memoized
def get_system_path_max():
# Choose a conservative default
@ -202,54 +144,6 @@ def get_system_path_max():
return sys_max_path_length
class Path:
"""
Describes the filepath separator types
in an enum style
with a helper attribute
exposing the path type of
the current platform.
"""
unix = 0
windows = 1
platform_path = windows if sys.platform == "win32" else unix
def format_os_path(path, mode=Path.unix):
"""
Format path to use consistent, platform specific
separators. Absolute paths are converted between
drive letters and a prepended '/' as per platform
requirement.
Parameters:
path (str): the path to be normalized, must be a string
or expose the replace method.
mode (Path): the path filesperator style to normalize the
passed path to. Default is unix style, i.e. '/'
"""
if not path:
return path
if mode == Path.windows:
path = path.replace("/", "\\")
else:
path = path.replace("\\", "/")
return path
def convert_to_posix_path(path):
return format_os_path(path, mode=Path.unix)
def convert_to_windows_path(path):
return format_os_path(path, mode=Path.windows)
def convert_to_platform_path(path):
return format_os_path(path, mode=Path.platform_path)
def substitute_config_variables(path):
"""Substitute placeholders into paths.

View File

@ -1,57 +0,0 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
def comma_list(sequence, article=""):
if type(sequence) is not list:
sequence = list(sequence)
if not sequence:
return
elif len(sequence) == 1:
return sequence[0]
else:
out = ", ".join(str(s) for s in sequence[:-1])
if len(sequence) != 2:
out += "," # oxford comma
out += " "
if article:
out += article + " "
out += str(sequence[-1])
return out
def comma_or(sequence):
return comma_list(sequence, "or")
def comma_and(sequence):
return comma_list(sequence, "and")
def quote(sequence, q="'"):
return ["%s%s%s" % (q, e, q) for e in sequence]
def plural(n, singular, plural=None, show_n=True):
"""Pluralize <singular> word by adding an s if n != 1.
Arguments:
n (int): number of things there are
singular (str): singular form of word
plural (str or None): optional plural form, for when it's not just
singular + 's'
show_n (bool): whether to include n in the result string (default True)
Returns:
(str): "1 thing" if n == 1 or "n things" if n != 1
"""
number = "%s " % n if show_n else ""
if n == 1:
return "%s%s" % (number, singular)
elif plural is not None:
return "%s%s" % (number, plural)
else:
return "%s%ss" % (number, singular)

View File

@ -14,7 +14,9 @@
import urllib.parse
import urllib.request
from spack.util.path import convert_to_posix_path, sanitize_filename
from llnl.path import convert_to_posix_path
from spack.util.path import sanitize_filename
def validate_scheme(scheme):

View File

@ -15,10 +15,10 @@
import llnl.util.lang as lang
import llnl.util.tty.color
from llnl.string import comma_or
import spack.directives
import spack.error as error
from spack.util.string import comma_or
special_variant_values = [None, "none", "*"]