diff --git a/CHANGELOG.md b/CHANGELOG.md index fa0a0f4b2..601351b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log + +## 2.47.10 (2019-10-26) +[Source](https://github.com/repos/nerdvegas/rez/tree/2.47.10) | [Diff](https://github.com/repos/nerdvegas/rez/compare/2.47.9...2.47.10) + +Notes + +* corrects failing tests in `rez/tests/test_packages.py` on windows +* adds %SYSTEMROOT% envvar to Python(ActionInterpreter) so `rez/tests/test_context.py` tests pass on windows + + ## 2.47.9 (2019-10-25) [Source](https://github.com/repos/nerdvegas/rez/tree/2.47.9) | [Diff](https://github.com/repos/nerdvegas/rez/compare/2.47.8...2.47.9) diff --git a/src/rez/rex.py b/src/rez/rex.py index 1fd3bdd03..4f0c22d74 100644 --- a/src/rez/rex.py +++ b/src/rez/rex.py @@ -591,6 +591,7 @@ def apply_environ(self): "interpreter before using it.") self.target_environ.update(self.manager.environ) + self.adjust_env_for_platform(self.target_environ) def get_output(self, style=OutputStyle.file): self.apply_environ() @@ -633,6 +634,7 @@ def error(self, value): def subprocess(self, args, **subproc_kwargs): if self.manager: self.target_environ.update(self.manager.environ) + self.adjust_env_for_platform(self.target_environ) shell_mode = isinstance(args, basestring) return Popen(args, @@ -684,6 +686,54 @@ def get_key_token(self, key): # here because the API requires it. return "${%s}" % key + def adjust_env_for_platform(self, env): + """ Make required platform-specific adjustments to env. + """ + if sys.platform.startswith('win'): + self._add_systemroot_to_env_win32(env) + + def _add_systemroot_to_env_win32(self, env): + """ Sets ``%SYSTEMROOT%`` environment variable, if not present + in :py:attr:`target_environ` . + + Args: + env (dict): desired environment variables + + Notes: + on windows, python-3.6 startup fails within an environment + where it ``%PATH%`` includes python3, but ``%SYSTEMROOT%`` is not + present. + + for example. + + .. code-block:: python + + from subprocess import Popen + cmds = ['python', '--version'] + + # successful + Popen(cmds) + Popen(cmds, env={'PATH': 'C:\\Python-3.6.5', + 'SYSTEMROOT': 'C:\Windows'}) + + # failure + Popen(cmds, env={'PATH': 'C:\\Python-3.6.5'}) + + #> Fatal Python Error: failed to get random numbers to initialize Python + + """ + # 'SYSTEMROOT' unecessary unless 'PATH' is set. + if env is None: + return + # leave SYSTEMROOT alone if set by user + if 'SYSTEMROOT' in env: + return + # not enough info to set SYSTEMROOT + if 'SYSTEMROOT' not in os.environ: + return + + env['SYSTEMROOT'] = os.environ['SYSTEMROOT'] + #=============================================================================== # String manipulation diff --git a/src/rez/tests/test_packages.py b/src/rez/tests/test_packages.py index 7a781696b..4fc798679 100644 --- a/src/rez/tests/test_packages.py +++ b/src/rez/tests/test_packages.py @@ -3,15 +3,17 @@ """ from rez.packages_ import iter_package_families, iter_packages, get_package, \ create_package, get_developer_package -from rez.package_resources_ import package_release_keys -from rez.package_repository import create_memory_package_repository from rez.package_py_utils import expand_requirement +from rez.package_repository import create_memory_package_repository +from rez.package_resources_ import package_release_keys from rez.tests.util import TestBase, TempdirMixin from rez.utils.formatting import PackageRequest +from rez.utils.platform_ import platform_ from rez.utils.sourcecode import SourceCode import unittest from rez.vendor.version.version import Version from rez.vendor.version.util import VersionError +from rez.utils.filesystem import canonical_path import os.path import os @@ -119,21 +121,20 @@ def test_2(self): def test_3(self): """check package contents.""" - # a py-based package package = get_package("versioned", "3.0") expected_data = dict( name="versioned", version=Version("3.0"), - base=os.path.join(self.py_packages_path, "versioned", "3.0"), + base=canonical_path(os.path.join(self.py_packages_path, "versioned", "3.0")), commands=SourceCode('env.PATH.append("{root}/bin")')) data = package.validated_data() self.assertDictEqual(data, expected_data) # a yaml-based package package = get_package("versioned", "2.0") - expected_uri = os.path.join(self.yaml_packages_path, - "versioned", "2.0", "package.yaml") + expected_uri = canonical_path(os.path.join(self.yaml_packages_path, + "versioned", "2.0", "package.yaml")) self.assertEqual(package.uri, expected_uri) # a py-based package with late binding attribute functions @@ -142,7 +143,7 @@ def test_3(self): # a 'combined' type package package = get_package("multi", "1.0") - expected_uri = os.path.join(self.yaml_packages_path, "multi.yaml<1.0>") + expected_uri = canonical_path(os.path.join(self.yaml_packages_path, "multi.yaml<1.0>")) self.assertEqual(package.uri, expected_uri) expected_data = dict( name="multi", @@ -153,7 +154,7 @@ def test_3(self): # a 'combined' type package, with version overrides package = get_package("multi", "1.1") - expected_uri = os.path.join(self.yaml_packages_path, "multi.yaml<1.1>") + expected_uri = canonical_path(os.path.join(self.yaml_packages_path, "multi.yaml<1.1>")) self.assertEqual(package.uri, expected_uri) expected_data = dict( name="multi", @@ -164,7 +165,7 @@ def test_3(self): # check that visibility of 'combined' packages is correct package = get_package("multi", "2.0") - expected_uri = os.path.join(self.py_packages_path, "multi.py<2.0>") + expected_uri = canonical_path(os.path.join(self.py_packages_path, "multi.py<2.0>")) self.assertEqual(package.uri, expected_uri) def test_4(self): @@ -309,11 +310,12 @@ def preprocess(this, data): def test_6(self): """test variant iteration.""" + base = canonical_path(os.path.join(self.py_packages_path, "variants_py", "2.0")) expected_data = dict( name="variants_py", version=Version("2.0"), description="package with variants", - base=os.path.join(self.py_packages_path, "variants_py", "2.0"), + base=base, requires=[PackageRequest("python-2.7")], commands=SourceCode('env.PATH.append("{root}/bin")')) diff --git a/src/rez/tests/test_utils.py b/src/rez/tests/test_utils.py new file mode 100644 index 000000000..c79721e76 --- /dev/null +++ b/src/rez/tests/test_utils.py @@ -0,0 +1,62 @@ +""" +unit tests for 'utils.filesystem' module +""" +import os +from rez.tests.util import TestBase +from rez.utils import filesystem +from rez.utils.platform_ import Platform, platform_ + + +class TestCanonicalPath(TestBase): + class CaseSensitivePlatform(Platform): + @property + def has_case_sensitive_filesystem(self): + return True + + class CaseInsensitivePlatform(Platform): + @property + def has_case_sensitive_filesystem(self): + return False + + def test_win32_case_insensitive(self): + if platform_.name != 'windows': + self.skipTest('on linux/macos, `os.path.realpath()` treats windows ' + 'abspaths as relpaths, and prepends `os.getcwd()`') + platform = self.CaseInsensitivePlatform() + path = filesystem.canonical_path('C:\\dir\\File.txt', platform) + expects = 'c:\\dir\\file.txt'.replace('\\', os.sep) + self.assertEqual(path, expects) + + def test_unix_case_sensistive_platform(self): + if platform_.name == 'windows': + self.skipTest('on windows, `os.path.realpath()` treats unix abspaths ' + 'as relpaths, and prepends `os.getcwd()`') + platform = self.CaseSensitivePlatform() + path = filesystem.canonical_path('/a/b/File.txt', platform) + expects = '/a/b/File.txt'.replace('\\', os.sep) + self.assertEqual(path, expects) + + def test_unix_case_insensistive_platform(self): + if platform_.name == 'windows': + self.skipTest('on windows, `os.path.realpath()` treats unix abspaths ' + 'as relpaths, and prepends `os.getcwd()`') + platform = self.CaseInsensitivePlatform() + path = filesystem.canonical_path('/a/b/File.txt', platform) + expects = '/a/b/file.txt'.replace('\\', os.sep) + self.assertEqual(path, expects) + + +# Copyright 2013-2016 Allan Johns. +# +# This library is free software: you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation, either +# version 3 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . diff --git a/src/rez/utils/_version.py b/src/rez/utils/_version.py index be052760f..7848f88d9 100644 --- a/src/rez/utils/_version.py +++ b/src/rez/utils/_version.py @@ -1,7 +1,7 @@ # Update this value to version up Rez. Do not place anything else in this file. -_rez_version = "2.47.9" +_rez_version = "2.47.10" # Copyright 2013-2016 Allan Johns. diff --git a/src/rez/utils/filesystem.py b/src/rez/utils/filesystem.py index b7a386dd4..5e8b8874b 100644 --- a/src/rez/utils/filesystem.py +++ b/src/rez/utils/filesystem.py @@ -19,6 +19,7 @@ import platform from rez.vendor.six import six +from rez.utils.platform_ import platform_ class TempDirs(object): @@ -424,7 +425,8 @@ def safe_chmod(path, mode): def to_nativepath(path): - return os.path.join(path.split('/')) + path = path.replace('\\', '/') + return os.path.join(*path.split('/')) def to_ntpath(path): @@ -435,6 +437,33 @@ def to_posixpath(path): return posixpath.sep.join(path.split(ntpath.sep)) +def canonical_path(path, platform=None): + """ Resolves symlinks, and formats filepath. + + Resolves symlinks, lowercases if filesystem is case-insensitive, + formats filepath using slashes appropriate for platform. + + Args: + path (str): + filepath being formatted + + platform (rez.utils.platform_.Platform, NoneType): + indicates platform path is being formatted for. + Defaults to current platform. + + Returns: + str: provided path, formatted for platform. + """ + if platform is None: + platform = platform_ + + path = os.path.normpath(os.path.realpath(path)) + + if not platform.has_case_sensitive_filesystem: + return path.lower() + return path + + def encode_filesystem_name(input_str): """Encodes an arbitrary unicode string to a generic filesystem-compatible non-unicode filename. diff --git a/src/rezplugins/package_repository/filesystem.py b/src/rezplugins/package_repository/filesystem.py index 8becadbce..0c2833641 100644 --- a/src/rezplugins/package_repository/filesystem.py +++ b/src/rezplugins/package_repository/filesystem.py @@ -21,7 +21,7 @@ from rez.utils.resources import cached_property from rez.utils.logging_ import print_warning from rez.utils.memcached import memcached, pool_memcached_connections -from rez.utils.filesystem import make_path_writable +from rez.utils.filesystem import make_path_writable, canonical_path from rez.utils.platform_ import platform_ from rez.serialise import load_from_file, FileFormat from rez.config import config @@ -470,8 +470,7 @@ def __init__(self, location, resource_pool): # ensure that differing case doesn't get interpreted as different repos # on case-insensitive platforms (eg windows) - if not platform_.has_case_sensitive_filesystem: - location = location.lower() + location = canonical_path(location, platform_) super(FileSystemPackageRepository, self).__init__(location, resource_pool)