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:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							f77a38a96b
						
					
				
				
					commit
					a236fce31f
				
			
							
								
								
									
										105
									
								
								lib/spack/llnl/path.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								lib/spack/llnl/path.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										67
									
								
								lib/spack/llnl/string.py
									
									
									
									
									
										Normal 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"
 | 
			
		||||
@@ -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))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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():
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = []
 | 
			
		||||
 
 | 
			
		||||
@@ -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]
 | 
			
		||||
 
 | 
			
		||||
@@ -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] = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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]
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								lib/spack/spack/test/llnl/llnl_string.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								lib/spack/spack/test/llnl/llnl_string.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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", "*"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user