summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn W. Parent <45471568+johnwparent@users.noreply.github.com>2022-01-21 15:46:11 -0500
committerPeter Scheibel <scheibel1@llnl.gov>2022-03-17 09:01:01 -0700
commite4d4a5193ff4ac2a19a7ae77d66df1cd085a979d (patch)
treea12ca6779b31dcf9a8d1ffa049c80f33ea655bc6
parentdf4129d3951b2dd7fed2193f853270fd2d62df0d (diff)
downloadspack-e4d4a5193ff4ac2a19a7ae77d66df1cd085a979d.tar.gz
spack-e4d4a5193ff4ac2a19a7ae77d66df1cd085a979d.tar.bz2
spack-e4d4a5193ff4ac2a19a7ae77d66df1cd085a979d.tar.xz
spack-e4d4a5193ff4ac2a19a7ae77d66df1cd085a979d.zip
Path handling (#28402)
Consolidate Spack's internal filepath logic to a select few places and refactor to consistent internal useage of os.path utilities. Creates a prefix, and a series of utilities in the path utility module that facilitate handling paths in a platform agnostic manner. Convert Windows paths to posix paths internally Prefer posixpath.join instead of os.path.join Updated util/ directory to account for Windows integration Co-authored-by: Stephen Crowell <stephen.crowell@khq.kitware.com> Co-authored-by: John Parent <john.parent@kitware.com> Module template format for windows (#23041)
-rw-r--r--lib/spack/llnl/util/filesystem.py150
-rw-r--r--lib/spack/spack/build_environment.py19
-rw-r--r--lib/spack/spack/compiler.py2
-rw-r--r--lib/spack/spack/compilers/aocc.py9
-rw-r--r--lib/spack/spack/compilers/arm.py9
-rw-r--r--lib/spack/spack/compilers/cce.py10
-rw-r--r--lib/spack/spack/compilers/clang.py29
-rw-r--r--lib/spack/spack/compilers/dpcpp.py10
-rw-r--r--lib/spack/spack/compilers/fj.py10
-rw-r--r--lib/spack/spack/compilers/gcc.py9
-rw-r--r--lib/spack/spack/compilers/intel.py9
-rw-r--r--lib/spack/spack/compilers/nag.py5
-rw-r--r--lib/spack/spack/compilers/nvhpc.py10
-rw-r--r--lib/spack/spack/compilers/oneapi.py9
-rw-r--r--lib/spack/spack/compilers/pgi.py10
-rw-r--r--lib/spack/spack/compilers/xl.py10
-rw-r--r--lib/spack/spack/compilers/xl_r.py10
-rw-r--r--lib/spack/spack/detection/common.py2
-rw-r--r--lib/spack/spack/fetch_strategy.py6
-rw-r--r--lib/spack/spack/modules/lmod.py5
-rw-r--r--lib/spack/spack/modules/tcl.py4
-rw-r--r--lib/spack/spack/paths.py67
-rw-r--r--lib/spack/spack/repo.py25
-rw-r--r--lib/spack/spack/stage.py3
-rw-r--r--lib/spack/spack/test/build_environment.py6
-rw-r--r--lib/spack/spack/test/cmd/style.py11
-rw-r--r--lib/spack/spack/test/llnl/util/file_list.py20
-rw-r--r--lib/spack/spack/test/llnl/util/filesystem.py24
-rw-r--r--lib/spack/spack/test/packages.py6
-rw-r--r--lib/spack/spack/util/debug.py4
-rw-r--r--lib/spack/spack/util/editor.py6
-rw-r--r--lib/spack/spack/util/environment.py19
-rw-r--r--lib/spack/spack/util/executable.py43
-rw-r--r--lib/spack/spack/util/path.py138
-rw-r--r--lib/spack/spack/util/url.py12
-rw-r--r--lib/spack/spack/util/web.py4
36 files changed, 493 insertions, 232 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 2a67ca698a..6fd6c140c8 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -5,19 +5,17 @@
import collections
import errno
import glob
-import grp
-import ctypes
import hashlib
import itertools
import numbers
import os
-import pwd
import re
import shutil
import stat
import sys
import tempfile
from contextlib import contextmanager
+from sys import platform as _platform
import six
@@ -27,6 +25,27 @@ from llnl.util.lang import dedupe, memoized
from llnl.util.symlink import symlink
from spack.util.executable import Executable
+from spack.util.path import path_to_os_path, system_path_filter
+
+is_windows = _platform == 'win32'
+
+if not is_windows:
+ import grp
+ import pwd
+else:
+ import win32security
+
+
+is_windows = _platform == 'win32'
+
+if not is_windows:
+ import grp
+ import pwd
+
+if sys.version_info >= (3, 3):
+ from collections.abc import Sequence # novm
+else:
+ from collections import Sequence
__all__ = [
'FileFilter',
@@ -76,7 +95,8 @@ __all__ = [
def getuid():
- if _platform == "win32":
+ if is_windows:
+ import ctypes
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
return 1
return 0
@@ -84,6 +104,7 @@ def getuid():
return os.getuid()
+@system_path_filter
def rename(src, dst):
# On Windows, os.rename will fail if the destination file already exists
if is_windows:
@@ -92,6 +113,7 @@ def rename(src, dst):
os.rename(src, dst)
+@system_path_filter
def path_contains_subdirectory(path, root):
norm_root = os.path.abspath(root).rstrip(os.path.sep) + os.path.sep
norm_path = os.path.abspath(path).rstrip(os.path.sep) + os.path.sep
@@ -116,6 +138,7 @@ def paths_containing_libs(paths, library_names):
required_lib_fnames = possible_library_filenames(library_names)
rpaths_to_include = []
+ paths = path_to_os_path(*paths)
for path in paths:
fnames = set(os.listdir(path))
if fnames & required_lib_fnames:
@@ -124,6 +147,7 @@ def paths_containing_libs(paths, library_names):
return rpaths_to_include
+@system_path_filter
def same_path(path1, path2):
norm1 = os.path.abspath(path1).rstrip(os.path.sep)
norm2 = os.path.abspath(path2).rstrip(os.path.sep)
@@ -174,7 +198,7 @@ def filter_file(regex, repl, *filenames, **kwargs):
if string:
regex = re.escape(regex)
-
+ filenames = path_to_os_path(*filenames)
for filename in filenames:
msg = 'FILTER FILE: {0} [replacing "{1}"]'
@@ -284,13 +308,39 @@ def change_sed_delimiter(old_delim, new_delim, *filenames):
repl = r's@\1@\2@g'
repl = repl.replace('@', new_delim)
-
+ filenames = path_to_os_path(*filenames)
for f in filenames:
filter_file(whole_lines, repl, f)
filter_file(single_quoted, "'%s'" % repl, f)
filter_file(double_quoted, '"%s"' % repl, f)
+@system_path_filter(arg_slice=slice(1))
+def get_owner_uid(path, err_msg=None):
+ if not os.path.exists(path):
+ mkdirp(path, mode=stat.S_IRWXU)
+
+ p_stat = os.stat(path)
+ if p_stat.st_mode & stat.S_IRWXU != stat.S_IRWXU:
+ tty.error("Expected {0} to support mode {1}, but it is {2}"
+ .format(path, stat.S_IRWXU, p_stat.st_mode))
+
+ raise OSError(errno.EACCES,
+ err_msg.format(path, path) if err_msg else "")
+ else:
+ p_stat = os.stat(path)
+
+ if _platform != "win32":
+ owner_uid = p_stat.st_uid
+ else:
+ sid = win32security.GetFileSecurity(
+ path, win32security.OWNER_SECURITY_INFORMATION) \
+ .GetSecurityDescriptorOwner()
+ owner_uid = win32security.LookupAccountSid(None, sid)[0]
+ return owner_uid
+
+
+@system_path_filter
def set_install_permissions(path):
"""Set appropriate permissions on the installed file."""
# If this points to a file maintained in a Spack prefix, it is assumed that
@@ -313,12 +363,17 @@ def group_ids(uid=None):
Returns:
(list of int): gids of groups the user is a member of
"""
+ if is_windows:
+ tty.warn("Function is not supported on Windows")
+ return []
+
if uid is None:
uid = getuid()
user = pwd.getpwuid(uid).pw_name
return [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
+@system_path_filter(arg_slice=slice(1))
def chgrp(path, group):
"""Implement the bash chgrp function on a single path"""
if is_windows:
@@ -331,6 +386,7 @@ def chgrp(path, group):
os.chown(path, -1, gid)
+@system_path_filter(arg_slice=slice(1))
def chmod_x(entry, perms):
"""Implements chmod, treating all executable bits as set using the chmod
utility's `+X` option.
@@ -344,6 +400,7 @@ def chmod_x(entry, perms):
os.chmod(entry, perms)
+@system_path_filter
def copy_mode(src, dest):
"""Set the mode of dest to that of src unless it is a link.
"""
@@ -360,6 +417,7 @@ def copy_mode(src, dest):
os.chmod(dest, dest_mode)
+@system_path_filter
def unset_executable_mode(path):
mode = os.stat(path).st_mode
mode &= ~stat.S_IXUSR
@@ -368,6 +426,7 @@ def unset_executable_mode(path):
os.chmod(path, mode)
+@system_path_filter
def copy(src, dest, _permissions=False):
"""Copy the file(s) *src* to the file or directory *dest*.
@@ -412,6 +471,7 @@ def copy(src, dest, _permissions=False):
copy_mode(src, dst)
+@system_path_filter
def install(src, dest):
"""Install the file(s) *src* to the file or directory *dest*.
@@ -430,6 +490,7 @@ def install(src, dest):
copy(src, dest, _permissions=True)
+@system_path_filter
def resolve_link_target_relative_to_the_link(link):
"""
os.path.isdir uses os.path.exists, which for links will check
@@ -444,6 +505,7 @@ def resolve_link_target_relative_to_the_link(link):
return os.path.join(link_dir, target)
+@system_path_filter
def copy_tree(src, dest, symlinks=True, ignore=None, _permissions=False):
"""Recursively copy an entire directory tree rooted at *src*.
@@ -528,6 +590,7 @@ def copy_tree(src, dest, symlinks=True, ignore=None, _permissions=False):
copy_mode(s, d)
+@system_path_filter
def install_tree(src, dest, symlinks=True, ignore=None):
"""Recursively install an entire directory tree rooted at *src*.
@@ -547,11 +610,13 @@ def install_tree(src, dest, symlinks=True, ignore=None):
copy_tree(src, dest, symlinks=symlinks, ignore=ignore, _permissions=True)
+@system_path_filter
def is_exe(path):
"""True if path is an executable file."""
return os.path.isfile(path) and os.access(path, os.X_OK)
+@system_path_filter
def get_filetype(path_name):
"""
Return the output of file path_name as a string to identify file type.
@@ -563,6 +628,7 @@ def get_filetype(path_name):
return output.strip()
+@system_path_filter(arg_slice=slice(1))
def chgrp_if_not_world_writable(path, group):
"""chgrp path to group if path is not world writable"""
mode = os.stat(path).st_mode
@@ -592,7 +658,7 @@ def mkdirp(*paths, **kwargs):
mode = kwargs.get('mode', None)
group = kwargs.get('group', None)
default_perms = kwargs.get('default_perms', 'args')
-
+ paths = path_to_os_path(*paths)
for path in paths:
if not os.path.exists(path):
try:
@@ -653,6 +719,7 @@ def mkdirp(*paths, **kwargs):
raise OSError(errno.EEXIST, "File already exists", path)
+@system_path_filter
def force_remove(*paths):
"""Remove files without printing errors. Like ``rm -f``, does NOT
remove directories."""
@@ -664,6 +731,7 @@ def force_remove(*paths):
@contextmanager
+@system_path_filter
def working_dir(dirname, **kwargs):
if kwargs.get('create', False):
mkdirp(dirname)
@@ -683,6 +751,7 @@ class CouldNotRestoreDirectoryBackup(RuntimeError):
@contextmanager
+@system_path_filter
def replace_directory_transaction(directory_name, tmp_root=None):
"""Moves a directory to a temporary space. If the operations executed
within the context manager don't raise an exception, the directory is
@@ -738,6 +807,7 @@ def replace_directory_transaction(directory_name, tmp_root=None):
tty.debug('Temporary directory deleted [{0}]'.format(tmp_dir))
+@system_path_filter
def hash_directory(directory, ignore=[]):
"""Hashes recursively the content of a directory.
@@ -766,6 +836,7 @@ def hash_directory(directory, ignore=[]):
@contextmanager
+@system_path_filter
def write_tmp_and_move(filename):
"""Write to a temporary file, then move into place."""
dirname = os.path.dirname(filename)
@@ -777,6 +848,7 @@ def write_tmp_and_move(filename):
@contextmanager
+@system_path_filter
def open_if_filename(str_or_file, mode='r'):
"""Takes either a path or a file object, and opens it if it is a path.
@@ -789,9 +861,13 @@ def open_if_filename(str_or_file, mode='r'):
yield str_or_file
+@system_path_filter
def touch(path):
"""Creates an empty file at the specified path."""
- perms = (os.O_WRONLY | os.O_CREAT | os.O_NONBLOCK | os.O_NOCTTY)
+ if is_windows:
+ perms = (os.O_WRONLY | os.O_CREAT)
+ else:
+ perms = (os.O_WRONLY | os.O_CREAT | os.O_NONBLOCK | os.O_NOCTTY)
fd = None
try:
fd = os.open(path, perms)
@@ -801,6 +877,7 @@ def touch(path):
os.close(fd)
+@system_path_filter
def touchp(path):
"""Like ``touch``, but creates any parent directories needed for the file.
"""
@@ -808,6 +885,7 @@ def touchp(path):
touch(path)
+@system_path_filter
def force_symlink(src, dest):
try:
symlink(src, dest)
@@ -816,6 +894,7 @@ def force_symlink(src, dest):
symlink(src, dest)
+@system_path_filter
def join_path(prefix, *args):
path = str(prefix)
for elt in args:
@@ -823,14 +902,16 @@ def join_path(prefix, *args):
return path
+@system_path_filter
def ancestor(dir, n=1):
"""Get the nth ancestor of a directory."""
parent = os.path.abspath(dir)
for i in range(n):
parent = os.path.dirname(parent)
- return parent
+ return parent.replace("\\", "/")
+@system_path_filter
def get_single_file(directory):
fnames = os.listdir(directory)
if len(fnames) != 1:
@@ -850,6 +931,7 @@ def temp_cwd():
@contextmanager
+@system_path_filter
def temp_rename(orig_path, temp_path):
same_path = os.path.realpath(orig_path) == os.path.realpath(temp_path)
if not same_path:
@@ -861,11 +943,13 @@ def temp_rename(orig_path, temp_path):
shutil.move(temp_path, orig_path)
+@system_path_filter
def can_access(file_name):
"""True if we have read/write access to the file."""
return os.access(file_name, os.R_OK | os.W_OK)
+@system_path_filter
def traverse_tree(source_root, dest_root, rel_path='', **kwargs):
"""Traverse two filesystem trees simultaneously.
@@ -948,6 +1032,7 @@ def traverse_tree(source_root, dest_root, rel_path='', **kwargs):
yield (source_path, dest_path)
+@system_path_filter
def set_executable(path):
mode = os.stat(path).st_mode
if mode & stat.S_IRUSR:
@@ -959,6 +1044,7 @@ def set_executable(path):
os.chmod(path, mode)
+@system_path_filter
def last_modification_time_recursive(path):
path = os.path.abspath(path)
times = [os.stat(path).st_mtime]
@@ -968,6 +1054,7 @@ def last_modification_time_recursive(path):
return max(times)
+@system_path_filter
def remove_empty_directories(root):
"""Ascend up from the leaves accessible from `root` and remove empty
directories.
@@ -984,6 +1071,7 @@ def remove_empty_directories(root):
pass
+@system_path_filter
def remove_dead_links(root):
"""Recursively removes any dead link that is present in root.
@@ -996,6 +1084,7 @@ def remove_dead_links(root):
remove_if_dead_link(path)
+@system_path_filter
def remove_if_dead_link(path):
"""Removes the argument if it is a dead link.
@@ -1006,6 +1095,7 @@ def remove_if_dead_link(path):
os.unlink(path)
+@system_path_filter
def remove_linked_tree(path):
"""Removes a directory and its contents.
@@ -1024,6 +1114,7 @@ def remove_linked_tree(path):
@contextmanager
+@system_path_filter
def safe_remove(*files_or_dirs):
"""Context manager to remove the files passed as input, but restore
them in case any exception is raised in the context block.
@@ -1070,6 +1161,7 @@ def safe_remove(*files_or_dirs):
raise
+@system_path_filter
def fix_darwin_install_name(path):
"""Fix install name of dynamic libraries on Darwin to have full path.
@@ -1156,6 +1248,10 @@ def find(root, files, recursive=True):
return _find_non_recursive(root, files)
+# here and in _find_non_recursive below we only take the first
+# index to check for system path safety as glob handles this
+# w.r.t. search_files
+@system_path_filter
def _find_recursive(root, search_files):
# The variable here is **on purpose** a defaultdict. The idea is that
@@ -1166,7 +1262,6 @@ def _find_recursive(root, search_files):
# Make the path absolute to have os.walk also return an absolute path
root = os.path.abspath(root)
-
for path, _, list_files in os.walk(root):
for search_file in search_files:
matches = glob.glob(os.path.join(path, search_file))
@@ -1180,6 +1275,7 @@ def _find_recursive(root, search_files):
return answer
+@system_path_filter
def _find_non_recursive(root, search_files):
# The variable here is **on purpose** a defaultdict as os.list_dir
# can return files in any order (does not preserve stability)
@@ -1311,7 +1407,7 @@ class HeaderList(FileList):
if isinstance(value, six.string_types):
value = [value]
- self._directories = [os.path.normpath(x) for x in value]
+ self._directories = [path_to_os_path(os.path.normpath(x))[0] for x in value]
def _default_directories(self):
"""Default computation of directories based on the list of
@@ -1469,6 +1565,7 @@ def find_headers(headers, root, recursive=False):
return HeaderList(find(root, headers, recursive))
+@system_path_filter
def find_all_headers(root):
"""Convenience function that returns the list of all headers found
in the directory passed as argument.
@@ -1696,6 +1793,7 @@ def find_libraries(libraries, root, shared=True, recursive=False):
return LibraryList(found_libs)
+@system_path_filter
@memoized
def can_access_dir(path):
"""Returns True if the argument is an accessible directory.
@@ -1709,6 +1807,7 @@ def can_access_dir(path):
return os.path.isdir(path) and os.access(path, os.R_OK | os.X_OK)
+@system_path_filter
@memoized
def can_write_to_dir(path):
"""Return True if the argument is a directory in which we can write.
@@ -1722,6 +1821,7 @@ def can_write_to_dir(path):
return os.path.isdir(path) and os.access(path, os.R_OK | os.X_OK | os.W_OK)
+@system_path_filter
@memoized
def files_in(*search_paths):
"""Returns all the files in paths passed as arguments.
@@ -1743,6 +1843,7 @@ def files_in(*search_paths):
return files
+@system_path_filter
def search_paths_for_executables(*path_hints):
"""Given a list of path hints returns a list of paths where
to search for an executable.
@@ -1770,6 +1871,7 @@ def search_paths_for_executables(*path_hints):
return executable_paths
+@system_path_filter
def partition_path(path, entry=None):
"""
Split the prefixes of the path at the first occurrence of entry and
@@ -1786,7 +1888,11 @@ def partition_path(path, entry=None):
# Derive the index of entry within paths, which will correspond to
# the location of the entry in within the path.
try:
- entries = path.split(os.sep)
+ sep = os.sep
+ entries = path.split(sep)
+ if entries[0].endswith(":"):
+ # Handle drive letters e.g. C:/ on Windows
+ entries[0] = entries[0] + sep
i = entries.index(entry)
if '' in entries:
i -= 1
@@ -1797,6 +1903,7 @@ def partition_path(path, entry=None):
return paths, '', []
+@system_path_filter
def prefixes(path):
"""
Returns a list containing the path and its ancestors, top-to-bottom.
@@ -1810,6 +1917,9 @@ def prefixes(path):
For example, path ``./hi/jkl/mn`` results in a list with the following
paths, in order: ``./hi``, ``./hi/jkl``, and ``./hi/jkl/mn``.
+ On Windows, paths will be normalized to use ``/`` and ``/`` will always
+ be used as the separator instead of ``os.sep``.
+
Parameters:
path (str): the string used to derive ancestor paths
@@ -1818,14 +1928,17 @@ def prefixes(path):
"""
if not path:
return []
-
- parts = path.strip(os.sep).split(os.sep)
- if path.startswith(os.sep):
- parts.insert(0, os.sep)
+ sep = os.sep
+ parts = path.strip(sep).split(sep)
+ if path.startswith(sep):
+ parts.insert(0, sep)
+ elif parts[0].endswith(":"):
+ # Handle drive letters e.g. C:/ on Windows
+ parts[0] = parts[0] + sep
paths = [os.path.join(*parts[:i + 1]) for i in range(len(parts))]
try:
- paths.remove(os.sep)
+ paths.remove(sep)
except ValueError:
pass
@@ -1837,6 +1950,7 @@ def prefixes(path):
return paths
+@system_path_filter
def md5sum(file):
"""Compute the MD5 sum of a file.
@@ -1852,6 +1966,7 @@ def md5sum(file):
return md5.digest()
+@system_path_filter
def remove_directory_contents(dir):
"""Remove all contents of a directory."""
if os.path.exists(dir):
@@ -1863,6 +1978,7 @@ def remove_directory_contents(dir):
@contextmanager
+@system_path_filter
def keep_modification_time(*filenames):
"""
Context manager to keep the modification timestamps of the input files.
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 3281a24013..bf0797a278 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -265,16 +265,16 @@ def set_compiler_environment_variables(pkg, env):
# Set SPACK compiler variables so that our wrapper knows what to call
if compiler.cc:
env.set('SPACK_CC', compiler.cc)
- env.set('CC', os.path.join(link_dir, compiler.link_paths['cc']))
+ env.set('CC', os.path.join(link_dir, os.path.join(compiler.link_paths['cc'])))
if compiler.cxx:
env.set('SPACK_CXX', compiler.cxx)
- env.set('CXX', os.path.join(link_dir, compiler.link_paths['cxx']))
+ env.set('CXX', os.path.join(link_dir, os.path.join(compiler.link_paths['cxx'])))
if compiler.f77:
env.set('SPACK_F77', compiler.f77)
- env.set('F77', os.path.join(link_dir, compiler.link_paths['f77']))
+ env.set('F77', os.path.join(link_dir, os.path.join(compiler.link_paths['f77'])))
if compiler.fc:
env.set('SPACK_FC', compiler.fc)
- env.set('FC', os.path.join(link_dir, compiler.link_paths['fc']))
+ env.set('FC', os.path.join(link_dir, os.path.join(compiler.link_paths['fc'])))
# Set SPACK compiler rpath flags so that our wrapper knows what to use
env.set('SPACK_CC_RPATH_ARG', compiler.cc_rpath_arg)
@@ -374,7 +374,8 @@ def set_wrapper_variables(pkg, env):
# directory. Add that to the path too.
env_paths = []
compiler_specific = os.path.join(
- spack.paths.build_env_path, os.path.dirname(pkg.compiler.link_paths['cc']))
+ spack.paths.build_env_path,
+ os.path.dirname(os.path.join(pkg.compiler.link_paths['cc'])))
for item in [spack.paths.build_env_path, compiler_specific]:
env_paths.append(item)
ci = os.path.join(item, 'case-insensitive')
@@ -537,10 +538,10 @@ def _set_variables_for_single_module(pkg, module):
# Put spack compiler paths in module scope.
link_dir = spack.paths.build_env_path
- m.spack_cc = os.path.join(link_dir, pkg.compiler.link_paths['cc'])
- m.spack_cxx = os.path.join(link_dir, pkg.compiler.link_paths['cxx'])
- m.spack_f77 = os.path.join(link_dir, pkg.compiler.link_paths['f77'])
- m.spack_fc = os.path.join(link_dir, pkg.compiler.link_paths['fc'])
+ m.spack_cc = os.path.join(link_dir, os.path.join(pkg.compiler.link_paths['cc']))
+ m.spack_cxx = os.path.join(link_dir, os.path.join(pkg.compiler.link_paths['cxx']))
+ m.spack_f77 = os.path.join(link_dir, os.path.join(pkg.compiler.link_paths['f77']))
+ m.spack_fc = os.path.join(link_dir, os.path.join(pkg.compiler.link_paths['fc']))
# Emulate some shell commands for convenience
m.pwd = os.getcwd
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
index cf903c129c..0a502efffa 100644
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -23,6 +23,7 @@ import spack.util.executable
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']
@@ -157,6 +158,7 @@ def _parse_link_paths(string):
return implicit_link_dirs
+@system_path_filter
def _parse_non_system_link_dirs(string):
"""Parses link paths out of compiler debug output.
diff --git a/lib/spack/spack/compilers/aocc.py b/lib/spack/spack/compilers/aocc.py
index d4bafe2795..345161bdeb 100644
--- a/lib/spack/spack/compilers/aocc.py
+++ b/lib/spack/spack/compilers/aocc.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import re
import sys
@@ -42,10 +43,10 @@ class Aocc(Compiler):
@property
def link_paths(self):
- link_paths = {'cc': 'aocc/clang',
- 'cxx': 'aocc/clang++',
- 'f77': 'aocc/flang',
- 'fc': 'aocc/flang'}
+ link_paths = {'cc': os.path.join('aocc', 'clang'),
+ 'cxx': os.path.join('aocc', 'clang++'),
+ 'f77': os.path.join('aocc', 'flang'),
+ 'fc': os.path.join('aocc', 'flang')}
return link_paths
diff --git a/lib/spack/spack/compilers/arm.py b/lib/spack/spack/compilers/arm.py
index 6b8229d498..bcafe2ae8c 100644
--- a/lib/spack/spack/compilers/arm.py
+++ b/lib/spack/spack/compilers/arm.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import re
import spack.compiler
@@ -22,10 +23,10 @@ class Arm(spack.compiler.Compiler):
fc_names = ['armflang']
# Named wrapper links within lib/spack/env
- link_paths = {'cc': 'arm/armclang',
- 'cxx': 'arm/armclang++',
- 'f77': 'arm/armflang',
- 'fc': 'arm/armflang'}
+ link_paths = {'cc': os.path.join('arm', 'armclang'),
+ 'cxx': os.path.join('arm', 'armclang++'),
+ 'f77': os.path.join('arm', 'armflang'),
+ 'fc': os.path.join('arm', 'armflang')}
# The ``--version`` option seems to be the most consistent one for
# arm compilers. Output looks like this:
diff --git a/lib/spack/spack/compilers/cce.py b/lib/spack/spack/compilers/cce.py
index 191023e831..b0a4fa1613 100644
--- a/lib/spack/spack/compilers/cce.py
+++ b/lib/spack/spack/compilers/cce.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import ver
@@ -27,10 +29,10 @@ class Cce(Compiler):
PrgEnv = 'PrgEnv-cray'
PrgEnv_compiler = 'cce'
- link_paths = {'cc': 'cce/cc',
- 'cxx': 'cce/case-insensitive/CC',
- 'f77': 'cce/ftn',
- 'fc': 'cce/ftn'}
+ link_paths = {'cc': os.path.join('cce', 'cc'),
+ 'cxx': os.path.join('cce', 'case-insensitive', 'CC'),
+ 'f77': os.path.join('cce', 'ftn'),
+ 'fc': os.path.join('cce', 'ftn')}
@property
def is_clang_based(self):
diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py
index e2d423d9c8..2802aad840 100644
--- a/lib/spack/spack/compilers/clang.py
+++ b/lib/spack/spack/compilers/clang.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import re
import sys
@@ -13,20 +14,20 @@ from spack.version import ver
#: compiler symlink mappings for mixed f77 compilers
f77_mapping = [
- ('gfortran', 'clang/gfortran'),
- ('xlf_r', 'xl_r/xlf_r'),
- ('xlf', 'xl/xlf'),
- ('pgfortran', 'pgi/pgfortran'),
- ('ifort', 'intel/ifort')
+ ('gfortran', ('clang', 'gfortran')),
+ ('xlf_r', ('xl_r', 'xlf_r')),
+ ('xlf', ('xl', 'xlf')),
+ ('pgfortran', ('pgi', 'pgfortran')),
+ ('ifort', ('intel', 'ifort'))
]
#: compiler symlink mappings for mixed f90/fc compilers
fc_mapping = [
- ('gfortran', 'clang/gfortran'),
- ('xlf90_r', 'xl_r/xlf90_r'),
- ('xlf90', 'xl/xlf90'),
- ('pgfortran', 'pgi/pgfortran'),
- ('ifort', 'intel/ifort')
+ ('gfortran', ('clang', 'gfortran')),
+ ('xlf90_r', ('xl_r', 'xlf90_r')),
+ ('xlf90', ('xl', 'xlf90')),
+ ('pgfortran', ('pgi', 'pgfortran')),
+ ('ifort', ('intel', 'ifort'))
]
@@ -60,8 +61,8 @@ class Clang(Compiler):
@property
def link_paths(self):
# clang links are always the same
- link_paths = {'cc': 'clang/clang',
- 'cxx': 'clang/clang++'}
+ link_paths = {'cc': os.path.join('clang', 'clang'),
+ 'cxx': os.path.join('clang', 'clang++')}
# fortran links need to look at the actual compiler names from
# compilers.yaml to figure out which named symlink to use
@@ -70,14 +71,14 @@ class Clang(Compiler):
link_paths['f77'] = link_path
break
else:
- link_paths['f77'] = 'clang/flang'
+ link_paths['f77'] = os.path.join('clang', 'flang')
for compiler_name, link_path in fc_mapping:
if self.fc and compiler_name in self.fc:
link_paths['fc'] = link_path
break
else:
- link_paths['fc'] = 'clang/flang'
+ link_paths['fc'] = os.path.join('clang', 'flang')
return link_paths
diff --git a/lib/spack/spack/compilers/dpcpp.py b/lib/spack/spack/compilers/dpcpp.py
index 787b8f357c..1458e8f019 100644
--- a/lib/spack/spack/compilers/dpcpp.py
+++ b/lib/spack/spack/compilers/dpcpp.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
import spack.compilers.oneapi
@@ -23,7 +25,7 @@ class Dpcpp(spack.compilers.oneapi.Oneapi):
cxx_names = ['dpcpp']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'oneapi/icx',
- 'cxx': 'oneapi/dpcpp',
- 'f77': 'oneapi/ifx',
- 'fc': 'oneapi/ifx'}
+ link_paths = {'cc': os.path.join('oneapi', 'icx'),
+ 'cxx': os.path.join('oneapi', 'dpcpp'),
+ 'f77': os.path.join('oneapi', 'ifx'),
+ 'fc': os.path.join('oneapi', 'ifx')}
diff --git a/lib/spack/spack/compilers/fj.py b/lib/spack/spack/compilers/fj.py
index 4dc75ac701..a12efd2208 100644
--- a/lib/spack/spack/compilers/fj.py
+++ b/lib/spack/spack/compilers/fj.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
import spack.compiler
@@ -20,10 +22,10 @@ class Fj(spack.compiler.Compiler):
fc_names = ['frt']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'fj/fcc',
- 'cxx': 'fj/case-insensitive/FCC',
- 'f77': 'fj/frt',
- 'fc': 'fj/frt'}
+ link_paths = {'cc': os.path.join('fj', 'fcc'),
+ 'cxx': os.path.join('fj', 'case-insensitive', 'FCC'),
+ 'f77': os.path.join('fj', 'frt'),
+ 'fc': os.path.join('fj', 'frt')}
version_argument = '--version'
version_regex = r'\((?:FCC|FRT)\) ([a-z\d.]+)'
diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py
index abbce721e8..60c4a75978 100644
--- a/lib/spack/spack/compilers/gcc.py
+++ b/lib/spack/spack/compilers/gcc.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import re
import spack.compiler
@@ -29,10 +30,10 @@ class Gcc(spack.compiler.Compiler):
suffixes = [r'-mp-\d+(?:\.\d+)?', r'-\d+(?:\.\d+)?', r'\d\d']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'gcc/gcc',
- 'cxx': 'gcc/g++',
- 'f77': 'gcc/gfortran',
- 'fc': 'gcc/gfortran'}
+ link_paths = {'cc': os.path.join('gcc', 'gcc'),
+ 'cxx': os.path.join('gcc', 'g++'),
+ 'f77': os.path.join('gcc', 'gfortran'),
+ 'fc': os.path.join('gcc', 'gfortran')}
PrgEnv = 'PrgEnv-gnu'
PrgEnv_compiler = 'gcc'
diff --git a/lib/spack/spack/compilers/intel.py b/lib/spack/spack/compilers/intel.py
index d52feebb4d..2101c75bd4 100644
--- a/lib/spack/spack/compilers/intel.py
+++ b/lib/spack/spack/compilers/intel.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import sys
from spack.compiler import Compiler, UnsupportedCompilerFlag
@@ -23,10 +24,10 @@ class Intel(Compiler):
fc_names = ['ifort']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'intel/icc',
- 'cxx': 'intel/icpc',
- 'f77': 'intel/ifort',
- 'fc': 'intel/ifort'}
+ link_paths = {'cc': os.path.join('intel', 'icc'),
+ 'cxx': os.path.join('intel', 'icpc'),
+ 'f77': os.path.join('intel', 'ifort'),
+ 'fc': os.path.join('intel', 'ifort')}
PrgEnv = 'PrgEnv-intel'
PrgEnv_compiler = 'intel'
diff --git a/lib/spack/spack/compilers/nag.py b/lib/spack/spack/compilers/nag.py
index f960bf874a..2e0fccab8e 100644
--- a/lib/spack/spack/compilers/nag.py
+++ b/lib/spack/spack/compilers/nag.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
from typing import List # novm
import spack.compiler
@@ -26,8 +27,8 @@ class Nag(spack.compiler.Compiler):
link_paths = {
'cc': 'cc',
'cxx': 'c++',
- 'f77': 'nag/nagfor',
- 'fc': 'nag/nagfor'}
+ 'f77': os.path.join('nag', 'nagfor'),
+ 'fc': os.path.join('nag', 'nagfor')}
version_argument = '-V'
version_regex = r'NAG Fortran Compiler Release ([0-9.]+)'
diff --git a/lib/spack/spack/compilers/nvhpc.py b/lib/spack/spack/compilers/nvhpc.py
index 4c8c31c662..bb4c28c695 100644
--- a/lib/spack/spack/compilers/nvhpc.py
+++ b/lib/spack/spack/compilers/nvhpc.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
from spack.compiler import Compiler
@@ -20,10 +22,10 @@ class Nvhpc(Compiler):
fc_names = ['nvfortran']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'nvhpc/nvc',
- 'cxx': 'nvhpc/nvc++',
- 'f77': 'nvhpc/nvfortran',
- 'fc': 'nvhpc/nvfortran'}
+ link_paths = {'cc': os.path.join('nvhpc', 'nvc'),
+ 'cxx': os.path.join('nvhpc', 'nvc++'),
+ 'f77': os.path.join('nvhpc', 'nvfortran'),
+ 'fc': os.path.join('nvhpc', 'nvfortran')}
PrgEnv = 'PrgEnv-nvhpc'
PrgEnv_compiler = 'nvhpc'
diff --git a/lib/spack/spack/compilers/oneapi.py b/lib/spack/spack/compilers/oneapi.py
index d26ab02696..fbc4d1cc81 100644
--- a/lib/spack/spack/compilers/oneapi.py
+++ b/lib/spack/spack/compilers/oneapi.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
from os.path import dirname
from spack.compiler import Compiler
@@ -22,10 +23,10 @@ class Oneapi(Compiler):
fc_names = ['ifx']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'oneapi/icx',
- 'cxx': 'oneapi/icpx',
- 'f77': 'oneapi/ifx',
- 'fc': 'oneapi/ifx'}
+ link_paths = {'cc': os.path.join('oneapi', 'icx'),
+ 'cxx': os.path.join('oneapi', 'icpx'),
+ 'f77': os.path.join('oneapi', 'ifx'),
+ 'fc': os.path.join('oneapi', 'ifx')}
PrgEnv = 'PrgEnv-oneapi'
PrgEnv_compiler = 'oneapi'
diff --git a/lib/spack/spack/compilers/pgi.py b/lib/spack/spack/compilers/pgi.py
index 670d98e9c5..36fcaf5398 100644
--- a/lib/spack/spack/compilers/pgi.py
+++ b/lib/spack/spack/compilers/pgi.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import ver
@@ -21,10 +23,10 @@ class Pgi(Compiler):
fc_names = ['pgfortran', 'pgf95', 'pgf90']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'pgi/pgcc',
- 'cxx': 'pgi/pgc++',
- 'f77': 'pgi/pgfortran',
- 'fc': 'pgi/pgfortran'}
+ link_paths = {'cc': os.path.join('pgi', 'pgcc'),
+ 'cxx': os.path.join('pgi', 'pgc++'),
+ 'f77': os.path.join('pgi', 'pgfortran'),
+ 'fc': os.path.join('pgi', 'pgfortran')}
PrgEnv = 'PrgEnv-pgi'
PrgEnv_compiler = 'pgi'
diff --git a/lib/spack/spack/compilers/xl.py b/lib/spack/spack/compilers/xl.py
index 4567402013..2d5d307ef6 100644
--- a/lib/spack/spack/compilers/xl.py
+++ b/lib/spack/spack/compilers/xl.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import ver
@@ -21,10 +23,10 @@ class Xl(Compiler):
fc_names = ['xlf90', 'xlf95', 'xlf2003', 'xlf2008']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'xl/xlc',
- 'cxx': 'xl/xlc++',
- 'f77': 'xl/xlf',
- 'fc': 'xl/xlf90'}
+ link_paths = {'cc': os.path.join('xl', 'xlc'),
+ 'cxx': os.path.join('xl', 'xlc++'),
+ 'f77': os.path.join('xl', 'xlf'),
+ 'fc': os.path.join('xl', 'xlf90')}
version_argument = '-qversion'
version_regex = r'([0-9]?[0-9]\.[0-9])'
diff --git a/lib/spack/spack/compilers/xl_r.py b/lib/spack/spack/compilers/xl_r.py
index d0d63a88b7..cf3e482729 100644
--- a/lib/spack/spack/compilers/xl_r.py
+++ b/lib/spack/spack/compilers/xl_r.py
@@ -3,6 +3,8 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+
import spack.compilers.xl
@@ -20,7 +22,7 @@ class XlR(spack.compilers.xl.Xl):
fc_names = ['xlf90_r', 'xlf95_r', 'xlf2003_r', 'xlf2008_r']
# Named wrapper links within build_env_path
- link_paths = {'cc': 'xl_r/xlc_r',
- 'cxx': 'xl_r/xlc++_r',
- 'f77': 'xl_r/xlf_r',
- 'fc': 'xl_r/xlf90_r'}
+ link_paths = {'cc': os.path.join('xl_r', 'xlc_r'),
+ 'cxx': os.path.join('xl_r', 'xlc++_r'),
+ 'f77': os.path.join('xl_r', 'xlf_r'),
+ 'fc': os.path.join('xl_r', 'xlf90_r')}
diff --git a/lib/spack/spack/detection/common.py b/lib/spack/spack/detection/common.py
index e24a0561da..62de85a652 100644
--- a/lib/spack/spack/detection/common.py
+++ b/lib/spack/spack/detection/common.py
@@ -218,6 +218,8 @@ def compute_windows_program_path_for_package(pkg):
pkg (spack.package.Package): package for which
Program Files location is to be computed
"""
+ # note windows paths are fine here as this method should only ever be invoked
+ # to interact with Windows
program_files = 'C:\\Program Files {}\\{}'
return[program_files.format(arch, name) for
diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py
index 09909c656e..0d2041b736 100644
--- a/lib/spack/spack/fetch_strategy.py
+++ b/lib/spack/spack/fetch_strategy.py
@@ -308,6 +308,9 @@ class URLFetchStrategy(FetchStrategy):
url = url.replace("\\", "/")
if sys.platform != "win32" and url.startswith('file://'):
path = urllib_parse.quote(url[len('file://'):])
+ if sys.platform == "win32":
+ if not path.startswith("/"):
+ path = "/" + path
url = 'file://' + path
urls.append(url)
@@ -681,6 +684,9 @@ class VCSFetchStrategy(FetchStrategy):
raise ValueError(
"%s requires %s argument." % (self.__class__, self.url_attr))
+ if sys.platform == "win32":
+ self.url = self.url.replace('\\', '/')
+
for attr in self.optional_attrs:
setattr(self, attr, kwargs.get(attr, None))
diff --git a/lib/spack/spack/modules/lmod.py b/lib/spack/spack/modules/lmod.py
index 0111f43229..e83f005854 100644
--- a/lib/spack/spack/modules/lmod.py
+++ b/lib/spack/spack/modules/lmod.py
@@ -6,6 +6,7 @@
import collections
import itertools
import os.path
+import posixpath
from typing import Any, Dict # novm
import llnl.util.lang as lang
@@ -99,7 +100,7 @@ def guess_core_compilers(name, store=False):
class LmodConfiguration(BaseConfiguration):
"""Configuration class for lmod module files."""
- default_projections = {'all': os.path.join('{name}', '{version}')}
+ default_projections = {'all': posixpath.join('{name}', '{version}')}
@property
def core_compilers(self):
@@ -449,7 +450,7 @@ class LmodContext(BaseContext):
class LmodModulefileWriter(BaseModuleFileWriter):
"""Writer class for lmod module files."""
- default_template = os.path.join('modules', 'modulefile.lua')
+ default_template = posixpath.join('modules', 'modulefile.lua')
class CoreCompilersNotFoundError(spack.error.SpackError, KeyError):
diff --git a/lib/spack/spack/modules/tcl.py b/lib/spack/spack/modules/tcl.py
index b224fef699..2a9e6ac047 100644
--- a/lib/spack/spack/modules/tcl.py
+++ b/lib/spack/spack/modules/tcl.py
@@ -6,7 +6,7 @@
"""This module implements the classes necessary to generate TCL
non-hierarchical modules.
"""
-import os.path
+import posixpath
import string
from typing import Any, Dict # novm
@@ -106,4 +106,4 @@ class TclContext(BaseContext):
class TclModulefileWriter(BaseModuleFileWriter):
"""Writer class for tcl module files."""
- default_template = os.path.join('modules', 'modulefile.tcl')
+ default_template = posixpath.join('modules', 'modulefile.tcl')
diff --git a/lib/spack/spack/paths.py b/lib/spack/spack/paths.py
index 4d8747d741..5e0c89f25c 100644
--- a/lib/spack/spack/paths.py
+++ b/lib/spack/spack/paths.py
@@ -10,6 +10,7 @@ throughout Spack and should bring in a minimal number of external
dependencies.
"""
import os
+import posixpath
import llnl.util.filesystem
@@ -20,41 +21,41 @@ prefix = llnl.util.filesystem.ancestor(__file__, 4)
spack_root = prefix
#: bin directory in the spack prefix
-bin_path = os.path.join(prefix, "bin")
+bin_path = posixpath.join(prefix, "bin")
#: The spack script itself
-spack_script = os.path.join(bin_path, "spack")
+spack_script = posixpath.join(bin_path, "spack")
#: The sbang script in the spack installation
-sbang_script = os.path.join(bin_path, "sbang")
+sbang_script = posixpath.join(bin_path, "sbang")
# spack directory hierarchy
-lib_path = os.path.join(prefix, "lib", "spack")
-external_path = os.path.join(lib_path, "external")
-build_env_path = os.path.join(lib_path, "env")
-module_path = os.path.join(lib_path, "spack")
-command_path = os.path.join(module_path, "cmd")
-analyzers_path = os.path.join(module_path, "analyzers")
-platform_path = os.path.join(module_path, 'platforms')
-compilers_path = os.path.join(module_path, "compilers")
-build_systems_path = os.path.join(module_path, 'build_systems')
-operating_system_path = os.path.join(module_path, 'operating_systems')
-test_path = os.path.join(module_path, "test")
-hooks_path = os.path.join(module_path, "hooks")
-opt_path = os.path.join(prefix, "opt")
-share_path = os.path.join(prefix, "share", "spack")
-etc_path = os.path.join(prefix, "etc")
+lib_path = posixpath.join(prefix, "lib", "spack")
+external_path = posixpath.join(lib_path, "external")
+build_env_path = posixpath.join(lib_path, "env")
+module_path = posixpath.join(lib_path, "spack")
+command_path = posixpath.join(module_path, "cmd")
+analyzers_path = posixpath.join(module_path, "analyzers")
+platform_path = posixpath.join(module_path, 'platforms')
+compilers_path = posixpath.join(module_path, "compilers")
+build_systems_path = posixpath.join(module_path, 'build_systems')
+operating_system_path = posixpath.join(module_path, 'operating_systems')
+test_path = posixpath.join(module_path, "test")
+hooks_path = posixpath.join(module_path, "hooks")
+opt_path = posixpath.join(prefix, "opt")
+share_path = posixpath.join(prefix, "share", "spack")
+etc_path = posixpath.join(prefix, "etc")
#
# Things in $spack/var/spack
#
-var_path = os.path.join(prefix, "var", "spack")
+var_path = posixpath.join(prefix, "var", "spack")
# read-only things in $spack/var/spack
-repos_path = os.path.join(var_path, "repos")
-packages_path = os.path.join(repos_path, "builtin")
-mock_packages_path = os.path.join(repos_path, "builtin.mock")
+repos_path = posixpath.join(var_path, "repos")
+packages_path = posixpath.join(repos_path, "builtin")
+mock_packages_path = posixpath.join(repos_path, "builtin.mock")
#
# Writable things in $spack/var/spack
@@ -62,13 +63,13 @@ mock_packages_path = os.path.join(repos_path, "builtin.mock")
# TODO: These should probably move to user cache, or some other location.
#
# fetch cache for downloaded files
-default_fetch_cache_path = os.path.join(var_path, "cache")
+default_fetch_cache_path = posixpath.join(var_path, "cache")
# GPG paths.
-gpg_keys_path = os.path.join(var_path, "gpg")
-mock_gpg_data_path = os.path.join(var_path, "gpg.mock", "data")
-mock_gpg_keys_path = os.path.join(var_path, "gpg.mock", "keys")
-gpg_path = os.path.join(opt_path, "spack", "gpg")
+gpg_keys_path = posixpath.join(var_path, "gpg")
+mock_gpg_data_path = posixpath.join(var_path, "gpg.mock", "data")
+mock_gpg_keys_path = posixpath.join(var_path, "gpg.mock", "keys")
+gpg_path = posixpath.join(opt_path, "spack", "gpg")
# Below paths are where Spack can write information for the user.
@@ -87,22 +88,22 @@ def _get_user_cache_path():
user_cache_path = _get_user_cache_path()
#: junit, cdash, etc. reports about builds
-reports_path = os.path.join(user_cache_path, "reports")
+reports_path = posixpath.join(user_cache_path, "reports")
#: installation test (spack test) output
-default_test_path = os.path.join(user_cache_path, "test")
+default_test_path = posixpath.join(user_cache_path, "test")
#: spack monitor analysis directories
-default_monitor_path = os.path.join(reports_path, "monitor")
+default_monitor_path = posixpath.join(reports_path, "monitor")
#: git repositories fetched to compare commits to versions
-user_repos_cache_path = os.path.join(user_cache_path, 'git_repos')
+user_repos_cache_path = posixpath.join(user_cache_path, 'git_repos')
#: bootstrap store for bootstrapping clingo and other tools
-default_user_bootstrap_path = os.path.join(user_cache_path, 'bootstrap')
+default_user_bootstrap_path = posixpath.join(user_cache_path, 'bootstrap')
#: transient caches for Spack data (virtual cache, patch sha256 lookup, etc.)
-default_misc_cache_path = os.path.join(user_cache_path, 'cache')
+default_misc_cache_path = posixpath.join(user_cache_path, 'cache')
# Below paths pull configuration from the host environment.
diff --git a/lib/spack/spack/repo.py b/lib/spack/spack/repo.py
index ea98882cf2..9bcca297f4 100644
--- a/lib/spack/spack/repo.py
+++ b/lib/spack/spack/repo.py
@@ -11,6 +11,7 @@ import importlib
import inspect
import itertools
import os
+import posixpath
import re
import shutil
import stat
@@ -137,6 +138,8 @@ class FastPackageChecker(Mapping):
def __init__(self, packages_path):
# The path of the repository managed by this instance
self.packages_path = packages_path
+ if sys.platform == 'win32':
+ self.packages_path = self.packages_path.replace("\\", "/")
# If the cache we need is not there yet, then build it appropriately
if packages_path not in self._paths_cache:
@@ -163,7 +166,7 @@ class FastPackageChecker(Mapping):
cache = {} # type: Dict[str, os.stat_result]
for pkg_name in os.listdir(self.packages_path):
# Skip non-directories in the package root.
- pkg_dir = os.path.join(self.packages_path, pkg_name)
+ pkg_dir = posixpath.join(self.packages_path, pkg_name)
# Warn about invalid names that look like packages.
if not nm.valid_module_name(pkg_name):
@@ -174,7 +177,7 @@ class FastPackageChecker(Mapping):
continue
# Construct the file name from the directory
- pkg_file = os.path.join(
+ pkg_file = posixpath.join(
self.packages_path, pkg_name, package_file_name
)
@@ -328,6 +331,8 @@ class RepoIndex(object):
def __init__(self, package_checker, namespace):
self.checker = package_checker
self.packages_path = self.checker.packages_path
+ if sys.platform == 'win32':
+ self.packages_path = self.packages_path.replace("\\", "/")
self.namespace = namespace
self.indexers = {}
@@ -705,6 +710,8 @@ class Repo(object):
# Root directory, containing _repo.yaml and package dirs
# Allow roots to by spack-relative by starting with '$spack'
self.root = spack.util.path.canonicalize_path(root)
+ if sys.platform == 'win32':
+ self.root = self.root.replace("\\", "/")
# check and raise BadRepoError on fail.
def check(condition, msg):
@@ -712,18 +719,18 @@ class Repo(object):
raise BadRepoError(msg)
# Validate repository layout.
- self.config_file = os.path.join(self.root, repo_config_name)
+ self.config_file = posixpath.join(self.root, repo_config_name)
check(os.path.isfile(self.config_file),
"No %s found in '%s'" % (repo_config_name, root))
- self.packages_path = os.path.join(self.root, packages_dir_name)
+ self.packages_path = posixpath.join(self.root, packages_dir_name)
check(os.path.isdir(self.packages_path),
"No directory '%s' found in '%s'" % (packages_dir_name, root))
# Read configuration and validate namespace
config = self._read_config()
check('namespace' in config, '%s must define a namespace.'
- % os.path.join(root, repo_config_name))
+ % posixpath.join(root, repo_config_name))
self.namespace = config['namespace']
check(re.match(r'[a-zA-Z][a-zA-Z0-9_.]+', self.namespace),
@@ -972,7 +979,7 @@ class Repo(object):
def dirname_for_package_name(self, pkg_name):
"""Get the directory name for a particular package. This is the
directory that contains its package.py file."""
- return os.path.join(self.packages_path, pkg_name)
+ return posixpath.join(self.packages_path, pkg_name)
def filename_for_package_name(self, pkg_name):
"""Get the filename for the module we should load for a particular
@@ -984,7 +991,7 @@ class Repo(object):
the package exists before importing.
"""
pkg_dir = self.dirname_for_package_name(pkg_name)
- return os.path.join(pkg_dir, package_file_name)
+ return posixpath.join(pkg_dir, package_file_name)
@property
def _pkg_checker(self):
@@ -1160,8 +1167,8 @@ def create_repo(root, namespace=None):
"Cannot create repository in %s: can't access parent!" % root)
try:
- config_path = os.path.join(root, repo_config_name)
- packages_path = os.path.join(root, packages_dir_name)
+ config_path = posixpath.join(root, repo_config_name)
+ packages_path = posixpath.join(root, packages_dir_name)
fs.mkdirp(packages_path)
with open(config_path, 'w') as config:
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 5a587ccee7..b92c0fbbc8 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -22,6 +22,7 @@ import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.filesystem import (
can_access,
+ get_owner_uid,
getuid,
install,
install_tree,
@@ -90,7 +91,7 @@ def create_stage_root(path):
for p in user_paths:
# Ensure access controls of subdirs from `$user` on down are
# restricted to the user.
- owner_uid = sup.get_owner_uid(p)
+ owner_uid = get_owner_uid(p)
if user_uid != owner_uid:
tty.warn("Expected user {0} to own {1}, but it is owned by {2}"
.format(user_uid, p, owner_uid))
diff --git a/lib/spack/spack/test/build_environment.py b/lib/spack/spack/test/build_environment.py
index 92bf6ebe32..b98b00da7b 100644
--- a/lib/spack/spack/test/build_environment.py
+++ b/lib/spack/spack/test/build_environment.py
@@ -205,7 +205,7 @@ def test_spack_paths_before_module_paths(
s.concretize()
pkg = s.package
- module_path = '/path/to/module'
+ module_path = os.path.join('path', 'to', 'module')
def _set_wrong_cc(x):
os.environ['PATH'] = module_path + os.pathsep + os.environ['PATH']
@@ -219,11 +219,9 @@ def test_spack_paths_before_module_paths(
spack.build_environment.setup_package(pkg, False)
- spack_path = posixpath.join(spack.paths.prefix, 'lib/spack/env')
+ spack_path = os.path.join(spack.paths.prefix, os.path.join('lib', 'spack', 'env'))
paths = os.environ['PATH'].split(os.pathsep)
- if sys.platform == 'win32':
- paths = [p.replace("\\", "/") for p in paths]
assert paths.index(spack_path) < paths.index(module_path)
diff --git a/lib/spack/spack/test/cmd/style.py b/lib/spack/spack/test/cmd/style.py
index ec450da535..4269f0bd28 100644
--- a/lib/spack/spack/test/cmd/style.py
+++ b/lib/spack/spack/test/cmd/style.py
@@ -5,7 +5,6 @@
import filecmp
import os
-import posixpath
import shutil
import sys
@@ -92,9 +91,6 @@ def test_changed_files(flake8_package):
for path in changed_files()
]
- if sys.platform == "win32":
- files = [f.replace("\\", "/") for f in files]
-
# There will likely be other files that have changed
# when these tests are run
assert flake8_package in files
@@ -125,9 +121,6 @@ def test_changed_files_all_files(flake8_package):
for path in changed_files(all_files=True)
])
- if sys.platform == "win32":
- files = [f.replace("\\", "/") for f in files]
-
# spack has a lot of files -- check that we're in the right ballpark
assert len(files) > 6000
@@ -136,13 +129,13 @@ def test_changed_files_all_files(flake8_package):
assert zlib.module.__file__ in files
# a core spack file
- assert posixpath.join(spack.paths.module_path, "spec.py") in files
+ assert os.path.join(spack.paths.module_path, "spec.py") in files
# a mock package
assert flake8_package in files
# this test
- assert __file__.replace("\\", "/") in files
+ assert __file__ in files
# ensure externals are excluded
assert not any(f.startswith(spack.paths.external_path) for f in files)
diff --git a/lib/spack/spack/test/llnl/util/file_list.py b/lib/spack/spack/test/llnl/util/file_list.py
index f121f91ccf..036cea500a 100644
--- a/lib/spack/spack/test/llnl/util/file_list.py
+++ b/lib/spack/spack/test/llnl/util/file_list.py
@@ -4,7 +4,7 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import fnmatch
-import posixpath
+import os.path
import pytest
import six
@@ -198,7 +198,7 @@ class TestHeaderList(object):
#: Directory where the data for the test below is stored
-search_dir = posixpath.join(spack.paths.test_path, 'data', 'directory_search')
+search_dir = os.path.join(spack.paths.test_path, 'data', 'directory_search')
@pytest.mark.parametrize('search_fn,search_list,root,kwargs', [
@@ -229,12 +229,12 @@ search_dir = posixpath.join(spack.paths.test_path, 'data', 'directory_search')
(find_headers, ['a', 'c'], search_dir, {'recursive': True}),
(find_libraries,
['liba', 'libd'],
- posixpath.join(search_dir, 'b'),
+ os.path.join(search_dir, 'b'),
{'recursive': False}
),
(find_headers,
['b', 'd'],
- posixpath.join(search_dir, 'b'),
+ os.path.join(search_dir, 'b'),
{'recursive': False}
),
])
@@ -270,14 +270,14 @@ def test_searching_order(search_fn, search_list, root, kwargs):
@pytest.mark.parametrize('root,search_list,kwargs,expected', [
(search_dir, '*/*bar.tx?', {'recursive': False}, [
- posixpath.join(search_dir, posixpath.join('a', 'foobar.txt')),
- posixpath.join(search_dir, posixpath.join('b', 'bar.txp')),
- posixpath.join(search_dir, posixpath.join('c', 'bar.txt')),
+ os.path.join(search_dir, os.path.join('a', 'foobar.txt')),
+ os.path.join(search_dir, os.path.join('b', 'bar.txp')),
+ os.path.join(search_dir, os.path.join('c', 'bar.txt')),
]),
(search_dir, '*/*bar.tx?', {'recursive': True}, [
- posixpath.join(search_dir, posixpath.join('a', 'foobar.txt')),
- posixpath.join(search_dir, posixpath.join('b', 'bar.txp')),
- posixpath.join(search_dir, posixpath.join('c', 'bar.txt')),
+ os.path.join(search_dir, os.path.join('a', 'foobar.txt')),
+ os.path.join(search_dir, os.path.join('b', 'bar.txp')),
+ os.path.join(search_dir, os.path.join('c', 'bar.txt')),
])
])
def test_find_with_globbing(root, search_list, kwargs, expected):
diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py
index aad95a1183..bb6914760d 100644
--- a/lib/spack/spack/test/llnl/util/filesystem.py
+++ b/lib/spack/spack/test/llnl/util/filesystem.py
@@ -415,8 +415,7 @@ def test_computation_of_header_directories(
def test_headers_directory_setter():
if sys.platform == "win32":
- # TODO: Test with \\'s
- root = "C:/pfx/include/subdir"
+ root = r'C:\pfx\include\subdir'
else:
root = "/pfx/include/subdir"
hl = fs.HeaderList(
@@ -453,14 +452,14 @@ def test_headers_directory_setter():
if sys.platform == "win32":
# TODO: Test \\s
paths = [
- ('C:/user/root', None,
- (['C:/', 'C:/user', 'C:/user/root'], '', [])),
- ('C:/user/root', 'C:/', ([], 'C:/', ['C:/user', 'C:/user/root'])),
- ('C:/user/root', 'user', (['C:/'], 'C:/user', ['C:/user/root'])),
- ('C:/user/root', 'root', (['C:/', 'C:/user'], 'C:/user/root', [])),
- ('relative/path', None, (['relative', 'relative/path'], '', [])),
- ('relative/path', 'relative', ([], 'relative', ['relative/path'])),
- ('relative/path', 'path', (['relative'], 'relative/path', []))
+ (r'C:\user\root', None,
+ (['C:\\', r'C:\user', r'C:\user\root'], '', [])),
+ (r'C:\user\root', 'C:\\', ([], 'C:\\', [r'C:\user', r'C:\user\root'])),
+ (r'C:\user\root', r'user', (['C:\\'], r'C:\user', [r'C:\user\root'])),
+ (r'C:\user\root', r'root', (['C:\\', r'C:\user'], r'C:\user\root', [])),
+ (r'relative\path', None, ([r'relative', r'relative\path'], '', [])),
+ (r'relative\path', r'relative', ([], r'relative', [r'relative\path'])),
+ (r'relative\path', r'path', ([r'relative'], r'relative\path', []))
]
else:
paths = [
@@ -483,9 +482,8 @@ def test_partition_path(path, entry, expected):
if sys.platform == "win32":
path_list = [
('', []),
- ('C:\\user\\dir', ['C:/', 'C:/user', 'C:/user/dir']),
- ('./some/sub/dir', ['./some', './some/sub', './some/sub/dir']),
- ('another/sub/dir', ['another', 'another/sub', 'another/sub/dir'])
+ (r'.\some\sub\dir', [r'.\some', r'.\some\sub', r'.\some\sub\dir']),
+ (r'another\sub\dir', [r'another', r'another\sub', r'another\sub\dir'])
]
else:
path_list = [
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index da8efce326..be8243fd6f 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -3,7 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
-import posixpath
+import os
import sys
import pytest
@@ -29,7 +29,7 @@ class TestPackage(object):
def test_package_filename(self):
repo = spack.repo.Repo(mock_packages_path)
filename = repo.filename_for_package_name('mpich')
- assert filename == posixpath.join(
+ assert filename == os.path.join(
mock_packages_path,
'packages',
'mpich',
@@ -39,7 +39,7 @@ class TestPackage(object):
def test_nonexisting_package_filename(self):
repo = spack.repo.Repo(mock_packages_path)
filename = repo.filename_for_package_name('some-nonexisting-package')
- assert filename == posixpath.join(
+ assert filename == os.path.join(
mock_packages_path,
'packages',
'some-nonexisting-package',
diff --git a/lib/spack/spack/util/debug.py b/lib/spack/spack/util/debug.py
index 8a15b76f16..580146d288 100644
--- a/lib/spack/spack/util/debug.py
+++ b/lib/spack/spack/util/debug.py
@@ -11,10 +11,10 @@ a stack trace and drops the user into an interpreter.
"""
import code
import os
+import pdb
import signal
-import traceback
import sys
-import pdb
+import traceback
def debug_handler(sig, frame):
diff --git a/lib/spack/spack/util/editor.py b/lib/spack/spack/util/editor.py
index dccd3a46d2..1a2c44f8ab 100644
--- a/lib/spack/spack/util/editor.py
+++ b/lib/spack/spack/util/editor.py
@@ -14,6 +14,7 @@ raising an EnvironmentError if we are unable to find one.
"""
import os
import shlex
+import sys
import llnl.util.tty as tty
@@ -39,8 +40,13 @@ def _find_exe_from_env_var(var):
if not exe:
return None, []
+ if sys.platform == "win32":
+ # Fix separators
+ exe = exe.replace('\\', '/')
+
# split env var into executable and args if needed
args = shlex.split(str(exe))
+
if not args:
return None, []
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
index 19e028ee67..da91414af0 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -26,6 +26,7 @@ import spack.platforms
import spack.spec
import spack.util.executable as executable
import spack.util.spack_json as sjson
+from spack.util.path import path_to_os_path, system_path_filter
system_paths = ['/', '/usr', '/usr/local']
suffixes = ['bin', 'bin64', 'include', 'lib', 'lib64']
@@ -130,6 +131,7 @@ def env_var_to_source_line(var, val):
return source_line
+@system_path_filter(arg_slice=slice(1))
def dump_environment(path, environment=None):
"""Dump an environment dictionary to a source-able file."""
use_env = environment or os.environ
@@ -143,6 +145,7 @@ def dump_environment(path, environment=None):
'\n']))
+@system_path_filter(arg_slice=slice(1))
def pickle_environment(path, environment=None):
"""Pickle an environment dictionary to a file."""
cPickle.dump(dict(environment if environment else os.environ),
@@ -307,7 +310,7 @@ class AppendPath(NameValueModifier):
environment_value = env.get(self.name, '')
directories = environment_value.split(
self.separator) if environment_value else []
- directories.append(os.path.normpath(self.value))
+ directories.append(path_to_os_path(os.path.normpath(self.value)).pop())
env[self.name] = self.separator.join(directories)
@@ -319,7 +322,8 @@ class PrependPath(NameValueModifier):
environment_value = env.get(self.name, '')
directories = environment_value.split(
self.separator) if environment_value else []
- directories = [os.path.normpath(self.value)] + directories
+ directories = [path_to_os_path(os.path.normpath(self.value)).pop()] \
+ + directories
env[self.name] = self.separator.join(directories)
@@ -331,8 +335,9 @@ class RemovePath(NameValueModifier):
environment_value = env.get(self.name, '')
directories = environment_value.split(
self.separator) if environment_value else []
- directories = [os.path.normpath(x) for x in directories
- if x != os.path.normpath(self.value)]
+ directories = [path_to_os_path(os.path.normpath(x)).pop()
+ for x in directories
+ if x != path_to_os_path(os.path.normpath(self.value)).pop()]
env[self.name] = self.separator.join(directories)
@@ -343,8 +348,8 @@ class DeprioritizeSystemPaths(NameModifier):
environment_value = env.get(self.name, '')
directories = environment_value.split(
self.separator) if environment_value else []
- directories = deprioritize_system_paths([os.path.normpath(x)
- for x in directories])
+ directories = deprioritize_system_paths(
+ [path_to_os_path(os.path.normpath(x)).pop() for x in directories])
env[self.name] = self.separator.join(directories)
@@ -356,7 +361,7 @@ class PruneDuplicatePaths(NameModifier):
environment_value = env.get(self.name, '')
directories = environment_value.split(
self.separator) if environment_value else []
- directories = prune_duplicate_paths([os.path.normpath(x)
+ directories = prune_duplicate_paths([path_to_os_path(os.path.normpath(x)).pop()
for x in directories])
env[self.name] = self.separator.join(directories)
diff --git a/lib/spack/spack/util/executable.py b/lib/spack/spack/util/executable.py
index f1e37a8672..9d8fe5f626 100644
--- a/lib/spack/spack/util/executable.py
+++ b/lib/spack/spack/util/executable.py
@@ -14,6 +14,7 @@ from six import string_types, text_type
import llnl.util.tty as tty
import spack.error
+from spack.util.path import Path, marshall_path, path_to_os_path, system_path_filter
__all__ = ['Executable', 'which', 'ProcessError']
@@ -22,9 +23,11 @@ class Executable(object):
"""Class representing a program that can be run on the command line."""
def __init__(self, name):
- if sys.platform == 'win32':
- name = name.replace('\\', '/')
+ # necesary here for the shlex call to succeed
+ name = marshall_path(name, mode=Path.unix)
self.exe = shlex.split(str(name))
+ # filter back to platform dependent path
+ self.exe = path_to_os_path(*self.exe)
self.default_env = {}
from spack.util.environment import EnvironmentModifications # no cycle
self.default_envmod = EnvironmentModifications()
@@ -33,10 +36,12 @@ class Executable(object):
if not self.exe:
raise ProcessError("Cannot construct executable for '%s'" % name)
+ @system_path_filter
def add_default_arg(self, arg):
"""Add a default argument to the command."""
self.exe.append(arg)
+ @system_path_filter
def add_default_env(self, key, value):
"""Set an environment variable when the command is run.
@@ -77,6 +82,7 @@ class Executable(object):
"""
return self.exe[0]
+ # needs a small fixup to better handle URLS and the like
def __call__(self, *args, **kwargs):
"""Run this executable in a subprocess.
@@ -204,12 +210,18 @@ class Executable(object):
if output in (str, str.split) or error in (str, str.split):
result = ''
if output in (str, str.split):
- outstr = text_type(out.decode('utf-8'))
+ if sys.platform == 'win32':
+ outstr = text_type(out.decode('ISO-8859-1'))
+ else:
+ outstr = text_type(out.decode('utf-8'))
result += outstr
if output is str.split:
sys.stdout.write(outstr)
if error in (str, str.split):
- errstr = text_type(err.decode('utf-8'))
+ if sys.platform == 'win32':
+ errstr = text_type(err.decode('ISO-8859-1'))
+ else:
+ errstr = text_type(err.decode('utf-8'))
result += errstr
if error is str.split:
sys.stderr.write(errstr)
@@ -263,6 +275,7 @@ class Executable(object):
return ' '.join(self.exe)
+@system_path_filter
def which_string(*args, **kwargs):
"""Like ``which()``, but return a string instead of an ``Executable``."""
path = kwargs.get('path', os.environ.get('PATH', ''))
@@ -272,15 +285,23 @@ def which_string(*args, **kwargs):
path = path.split(os.pathsep)
for name in args:
- if os.path.sep in name:
- exe = os.path.abspath(name)
- if os.path.isfile(exe) and os.access(exe, os.X_OK):
- return exe
- else:
- for directory in path:
- exe = os.path.join(directory, name)
+ win_candidates = []
+ if sys.platform == "win32" and (not name.endswith(".exe")
+ and not name.endswith(".bat")):
+ win_candidates = [name + ext for ext in ['.exe', '.bat']]
+ candidate_names = [name] if not win_candidates else win_candidates
+
+ for candidate_name in candidate_names:
+ if os.path.sep in candidate_name:
+ exe = os.path.abspath(candidate_name)
if os.path.isfile(exe) and os.access(exe, os.X_OK):
return exe
+ else:
+ for directory in path:
+ directory = path_to_os_path(directory).pop()
+ exe = os.path.join(directory, candidate_name)
+ if os.path.isfile(exe) and os.access(exe, os.X_OK):
+ return exe
if required:
raise CommandNotFoundError(
diff --git a/lib/spack/spack/util/path.py b/lib/spack/spack/util/path.py
index e83e3cb734..c8fb9aaea4 100644
--- a/lib/spack/spack/util/path.py
+++ b/lib/spack/spack/util/path.py
@@ -8,37 +8,38 @@
TODO: this is really part of spack.config. Consolidate it.
"""
import contextlib
-import errno
import getpass
import os
import re
-import stat
import subprocess
+import sys
import tempfile
-from sys import platform as _platform
+
+from six.moves.urllib.parse import urlparse
import llnl.util.tty as tty
-from llnl.util.filesystem import mkdirp
from llnl.util.lang import memoized
-import spack.paths
import spack.util.spack_yaml as syaml
-if _platform == "win32":
- import win32security
+is_windows = sys.platform == 'win32'
__all__ = [
'substitute_config_variables',
'substitute_path_variables',
'canonicalize_path']
+
# Substitutions to perform
-replacements = {
- 'spack': spack.paths.prefix,
- 'user': getpass.getuser(),
- 'tempdir': tempfile.gettempdir(),
- 'user_cache_path': spack.paths.user_cache_path,
-}
+def replacements():
+ # break circular import from spack.util.executable
+ import spack.paths
+ return {
+ 'spack': spack.paths.prefix,
+ 'user': getpass.getuser(),
+ 'tempdir': tempfile.gettempdir(),
+ 'user_cache_path': spack.paths.user_cache_path}
+
# This is intended to be longer than the part of the install path
# spack generates from the root path we give it. Included in the
@@ -60,6 +61,62 @@ SPACK_MAX_INSTALL_PATH_LENGTH = 300
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 path_to_os_path(*pths):
+ """
+ Takes an arbitrary number of postional 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 type(pth) is str and\
+ not is_path_url(pth):
+ pth = marshall_path(pth, mode=Path.platform_path)
+ ret_pths.append(pth)
+ return ret_pths
+
+
+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 useage 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
@@ -77,28 +134,37 @@ def get_system_path_max():
return sys_max_path_length
-def get_owner_uid(path, err_msg=None):
- if not os.path.exists(path):
- mkdirp(path, mode=stat.S_IRWXU)
+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 is_windows\
+ else unix
- p_stat = os.stat(path)
- if p_stat.st_mode & stat.S_IRWXU != stat.S_IRWXU:
- tty.error("Expected {0} to support mode {1}, but it is {2}"
- .format(path, stat.S_IRWXU, p_stat.st_mode))
- raise OSError(errno.EACCES,
- err_msg.format(path, path) if err_msg else "")
- else:
- p_stat = os.stat(path)
+def marshall_path(path, mode=Path.unix):
+ """
+ Format path to use consistent, platform specific
+ separators.
+
+ 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 _platform != "win32":
- owner_uid = p_stat.st_uid
+ """
+ if mode == Path.windows:
+ path = path.replace('/', '\\')
else:
- sid = win32security.GetFileSecurity(
- path, win32security.OWNER_SECURITY_INFORMATION) \
- .GetSecurityDescriptorOwner()
- owner_uid = win32security.LookupAccountSid(None, sid)[0]
- return owner_uid
+ path = path.replace('\\', '/')
+ return path
def substitute_config_variables(path):
@@ -118,17 +184,18 @@ def substitute_config_variables(path):
environment yaml files.
"""
import spack.environment as ev # break circular
+ _replacements = replacements()
env = ev.active_environment()
if env:
- replacements.update({'env': env.path})
+ _replacements.update({'env': env.path})
else:
# If a previous invocation added env, remove it
- replacements.pop('env', None)
+ _replacements.pop('env', None)
# Look up replacements
def repl(match):
m = match.group(0).strip('${}')
- return replacements.get(m.lower(), match.group(0))
+ return _replacements.get(m.lower(), match.group(0))
# Replace $var or ${var}.
return re.sub(r'(\$\w+\b|\$\{\w+\})', repl, path)
@@ -247,7 +314,7 @@ def padding_filter(string):
"""
global _filter_re
- pad = spack.util.path.SPACK_PATH_PADDING_CHARS
+ pad = SPACK_PATH_PADDING_CHARS
if not _filter_re:
longest_prefix = longest_prefix_re(pad)
regex = (
@@ -275,6 +342,7 @@ def filter_padding():
This is needed because Spack's debug output gets extremely long when we use a
long padded installation path.
"""
+ import spack.config
padding = spack.config.get("config:install_tree:padded_length", None)
if padding:
# filter out all padding from the intsall command output
diff --git a/lib/spack/spack/util/url.py b/lib/spack/spack/util/url.py
index 34c387d57d..656c13fa87 100644
--- a/lib/spack/spack/util/url.py
+++ b/lib/spack/spack/util/url.py
@@ -11,9 +11,11 @@ import itertools
import ntpath
import posixpath
import re
+import sys
import six.moves.urllib.parse as urllib_parse
from six import string_types
+from six.moves.urllib.request import url2pathname
import spack.util.path
@@ -75,6 +77,7 @@ def parse(url, scheme='file'):
if isinstance(url, string_types) else url)
(scheme, netloc, path, params, query, _) = url_obj
+
scheme = (scheme or 'file').lower()
# This is the first way that a windows path can be parsed.
@@ -120,6 +123,9 @@ def parse(url, scheme='file'):
if update_netloc:
netloc, path = path[:2], path[2:]
+ if sys.platform == "win32":
+ path = path.replace('\\', '/')
+
return urllib_parse.ParseResult(scheme=scheme,
netloc=netloc,
path=path,
@@ -194,7 +200,8 @@ def join(base_url, path, *extra, **kwargs):
'file:///opt/spack'
"""
paths = [
- (x if isinstance(x, string_types) else x.geturl())
+ (x.replace('\\', '/') if isinstance(x, string_types)
+ else x.geturl().replace('\\', '/'))
for x in itertools.chain((base_url, path), extra)]
n = len(paths)
last_abs_component = None
@@ -289,6 +296,9 @@ def _join(base_url, path, *extra, **kwargs):
netloc = path_tokens.pop(0)
base_path = posixpath.join('', *path_tokens)
+ if sys.platform == "win32":
+ base_path = base_path.replace('\\', '/')
+
return format(urllib_parse.ParseResult(scheme=scheme,
netloc=netloc,
path=base_path,
diff --git a/lib/spack/spack/util/web.py b/lib/spack/spack/util/web.py
index 34f4c3b0c6..f42087dcc6 100644
--- a/lib/spack/spack/util/web.py
+++ b/lib/spack/spack/util/web.py
@@ -165,6 +165,9 @@ def warn_no_ssl_cert_checking():
def push_to_url(
local_file_path, remote_path, keep_original=True, extra_args=None):
+ if sys.platform == "win32":
+ if remote_path[1] == ':':
+ remote_path = "file:///" + remote_path
remote_url = url_util.parse(remote_path)
verify_ssl = spack.config.get('config:verify_ssl')
@@ -649,6 +652,7 @@ def find_versions_of_archive(
versions = {}
matched = set()
for url in archive_urls + sorted(links):
+ url = url.replace("\\", "/")
if any(re.search(r, url) for r in regexes):
try:
ver = spack.url.parse_version(url)