diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py index 245aa44db87..f7bc478b727 100644 --- a/conan/tools/env/environment.py +++ b/conan/tools/env/environment.py @@ -4,7 +4,7 @@ from collections import OrderedDict from contextlib import contextmanager -from conan.tools.microsoft.subsystems import deduce_subsystem, WINDOWS +from conans.client.subsystems import deduce_subsystem, WINDOWS, subsystem_path from conans.errors import ConanException from conans.util.files import save @@ -14,7 +14,6 @@ class _EnvVarPlaceHolder: def environment_wrap_command(env_filenames, cmd, subsystem=None, cwd=None): - from conan.tools.microsoft.subsystems import subsystem_path assert env_filenames filenames = [env_filenames] if not isinstance(env_filenames, list) else env_filenames bats, shs = [], [] @@ -131,7 +130,6 @@ def get_str(self, placeholder, subsystem, pathsep): values.append(placeholder.format(name=self._name)) else: if self._path: - from conan.tools.microsoft.subsystems import subsystem_path v = subsystem_path(subsystem, v) values.append(v) if self._path: diff --git a/conan/tools/gnu/autotools.py b/conan/tools/gnu/autotools.py index 8308cc8330a..8776e6dd705 100644 --- a/conan/tools/gnu/autotools.py +++ b/conan/tools/gnu/autotools.py @@ -2,7 +2,7 @@ from conan.tools.build import build_jobs from conan.tools.files.files import load_toolchain_args -from conan.tools.microsoft.subsystems import subsystem_path, deduce_subsystem +from conans.client.subsystems import subsystem_path, deduce_subsystem from conans.client.build import join_arguments diff --git a/conan/tools/gnu/gnudeps_flags.py b/conan/tools/gnu/gnudeps_flags.py index 520bd3dc967..53227e13c5b 100644 --- a/conan/tools/gnu/gnudeps_flags.py +++ b/conan/tools/gnu/gnudeps_flags.py @@ -5,7 +5,7 @@ from conan.tools.microsoft import is_msvc from conan.tools.apple.apple import is_apple_os -from conan.tools.microsoft.subsystems import subsystem_path, deduce_subsystem +from conans.client.subsystems import subsystem_path, deduce_subsystem class GnuDepsFlags(object): diff --git a/conan/tools/microsoft/__init__.py b/conan/tools/microsoft/__init__.py index e86a8b19dd3..7ddb614d6f0 100644 --- a/conan/tools/microsoft/__init__.py +++ b/conan/tools/microsoft/__init__.py @@ -2,5 +2,5 @@ from conan.tools.microsoft.msbuild import MSBuild from conan.tools.microsoft.msbuilddeps import MSBuildDeps from conan.tools.microsoft.visual import msvc_runtime_flag, VCVars, is_msvc, is_msvc_static_runtime -from conan.tools.microsoft.subsystems import subsystem_path from conan.tools.microsoft.layout import vs_layout +from conan.tools.microsoft.subsystems import unix_path diff --git a/conan/tools/microsoft/subsystems.py b/conan/tools/microsoft/subsystems.py index d75a9ba62f1..651b9cb69b5 100644 --- a/conan/tools/microsoft/subsystems.py +++ b/conan/tools/microsoft/subsystems.py @@ -1,174 +1,8 @@ -import os -import platform -import re -import subprocess +from conans.client.subsystems import deduce_subsystem, subsystem_path -from conans.errors import ConanException -WINDOWS = "windows" -MSYS2 = 'msys2' -MSYS = 'msys' -CYGWIN = 'cygwin' -WSL = 'wsl' # Windows Subsystem for Linux -SFU = 'sfu' # Windows Services for UNIX - - -def run_in_windows_bash(conanfile, command, cwd=None, env=None): - from conan.tools.env import Environment - from conan.tools.env.environment import environment_wrap_command - """ Will run a unix command inside a bash terminal It requires to have MSYS2, CYGWIN, or WSL""" - if env: - # Passing env invalidates the conanfile.environment_scripts - env_win = [env] if not isinstance(env, list) else env - env_shell = [] - else: - env_shell = ["conanbuild.sh"] - env_win = ["conanbuild.bat"] - - subsystem = conanfile.conf.get("tools.microsoft.bash:subsystem") - shell_path = conanfile.conf.get("tools.microsoft.bash:path") - - if not platform.system() == "Windows": - raise ConanException("Command only for Windows operating system") - - if not subsystem or not shell_path: - raise ConanException("The config 'tools.microsoft.bash:subsystem' and 'tools.microsoft.bash:path' are " - "needed to run commands in a Windows subsystem") - if subsystem == MSYS2: - # Configure MSYS2 to inherith the PATH - msys2_mode_env = Environment() - _msystem = {"x86": "MINGW32"}.get(conanfile.settings.get_safe("arch"), "MINGW64") - msys2_mode_env.define("MSYSTEM", _msystem) - msys2_mode_env.define("MSYS2_PATH_TYPE", "inherit") - path = os.path.join(conanfile.generators_folder, "msys2_mode.bat") - msys2_mode_env.vars(conanfile, "build").save_bat(path) - env_win.append(path) - - # Needed to change to that dir inside the bash shell - wrapped_shell = '"%s"' % shell_path if " " in shell_path else shell_path - if env_win: - wrapped_shell = environment_wrap_command(env_win, shell_path, - cwd=conanfile.generators_folder) - - cwd = cwd or os.getcwd() - if not os.path.isabs(cwd): - cwd = os.path.join(os.getcwd(), cwd) - cwd_inside = subsystem_path(subsystem, cwd) - wrapped_user_cmd = command - if env_shell: - # Wrapping the inside_command enable to prioritize our environment, otherwise /usr/bin go - # first and there could be commands that we want to skip - wrapped_user_cmd = environment_wrap_command(env_shell, command, - cwd=conanfile.generators_folder) - inside_command = 'cd "{cwd_inside}" && ' \ - '{wrapped_user_cmd}'.format(cwd_inside=cwd_inside, - wrapped_user_cmd=wrapped_user_cmd) - - inside_command = escape_windows_cmd(inside_command) - - final_command = 'cd "{cwd}" && {wrapped_shell} --login -c {inside_command}'.format( - cwd=cwd, - wrapped_shell=wrapped_shell, - inside_command=inside_command) - conanfile.output.info('Running in windows bash: %s' % final_command) - return conanfile._conan_runner(final_command, output=conanfile.output, subprocess=True) - - -def escape_windows_cmd(command): - """ To use in a regular windows cmd.exe - 1. Adds escapes so the argument can be unpacked by CommandLineToArgvW() - 2. Adds escapes for cmd.exe so the argument survives cmd.exe's substitutions. - - Useful to escape commands to be executed in a windows bash (msys2, cygwin etc) - """ - quoted_arg = subprocess.list2cmdline([command]) - return "".join(["^%s" % arg if arg in r'()%!^"<>&|' else arg for arg in quoted_arg]) - - -def deduce_subsystem(conanfile, scope): - if scope.startswith("build"): - if hasattr(conanfile, "settings_build"): - the_os = conanfile.settings_build.get_safe("os") - subsystem = conanfile.settings_build.get_safe("os.subsystem") - else: - the_os = platform.system() # FIXME: Temporary fallback until 2.0 - subsystem = None - else: - the_os = conanfile.settings.get_safe("os") - subsystem = conanfile.settings.get_safe("os.subsystem") - - if not str(the_os).startswith("Windows"): - return None - - if subsystem is None and not scope.startswith("build"): # "run" scope do not follow win_bash - return WINDOWS - - if subsystem is None: # Not defined by settings, so native windows - if not conanfile.win_bash: - return WINDOWS - - subsystem = conanfile.conf.get("tools.microsoft.bash:subsystem") - if not subsystem: - raise ConanException("The config 'tools.microsoft.bash:subsystem' is " - "needed to run commands in a Windows subsystem") - return subsystem - - -def subsystem_path(subsystem, path): - """"Used to translate windows paths to MSYS unix paths like - c/users/path/to/file. Not working in a regular console or MinGW! - """ - if subsystem is None or subsystem == WINDOWS: +def unix_path(conanfile, path): + if not conanfile.win_bash: return path - - if os.path.exists(path): - # if the path doesn't exist (and abs) we cannot guess the casing - path = get_cased_path(path) - - if path.startswith('\\\\?\\'): - path = path[4:] - path = path.replace(":/", ":\\") - append_prefix = re.match(r'[a-z]:\\', path, re.IGNORECASE) - pattern = re.compile(r'([a-z]):\\', re.IGNORECASE) - path = pattern.sub('/\\1/', path).replace('\\', '/') - - if append_prefix: - if subsystem in (MSYS, MSYS2): - return path.lower() - elif subsystem == CYGWIN: - return '/cygdrive' + path.lower() - elif subsystem == WSL: - return '/mnt' + path[0:2].lower() + path[2:] - elif subsystem == SFU: - path = path.lower() - return '/dev/fs' + path[0] + path[1:].capitalize() - else: - return path if subsystem == WSL else path.lower() - return None - - -def get_cased_path(name): - if platform.system() != "Windows": - return name - if not os.path.isabs(name): - name = os.path.abspath(name) - - result = [] - current = name - while True: - parent, child = os.path.split(current) - if parent == current: - break - - child_cased = child - if os.path.exists(parent): - children = os.listdir(parent) - for c in children: - if c.upper() == child.upper(): - child_cased = c - break - result.append(child_cased) - current = parent - drive, _ = os.path.splitdrive(current) - result.append(drive) - return os.sep.join(reversed(result)) + subsystem = deduce_subsystem(conanfile, scope="build") + return subsystem_path(subsystem, path) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 46629495587..526afd6ca27 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -3,11 +3,11 @@ from os.path import join from conan.tools.env import VirtualRunEnv -from conan.tools.microsoft.subsystems import deduce_subsystem from conans.client.generators.cmake_find_package import CMakeFindPackageGenerator from conans.client.generators.cmake_find_package_multi import CMakeFindPackageMultiGenerator from conans.client.generators.compiler_args import CompilerArgsGenerator from conans.client.generators.pkg_config import PkgConfigGenerator +from conans.client.subsystems import deduce_subsystem, subsystem_path from conans.errors import ConanException, conanfile_exception_formatter from conans.util.env_reader import get_env from conans.util.files import normalize, save, mkdir @@ -264,7 +264,6 @@ def write_toolchain(conanfile, path, output): def _generate_aggregated_env(conanfile): - from conan.tools.microsoft.subsystems import subsystem_path def deactivates(filenames): # FIXME: Probably the order needs to be reversed diff --git a/conans/client/subsystems.py b/conans/client/subsystems.py new file mode 100644 index 00000000000..d75a9ba62f1 --- /dev/null +++ b/conans/client/subsystems.py @@ -0,0 +1,174 @@ +import os +import platform +import re +import subprocess + +from conans.errors import ConanException + +WINDOWS = "windows" +MSYS2 = 'msys2' +MSYS = 'msys' +CYGWIN = 'cygwin' +WSL = 'wsl' # Windows Subsystem for Linux +SFU = 'sfu' # Windows Services for UNIX + + +def run_in_windows_bash(conanfile, command, cwd=None, env=None): + from conan.tools.env import Environment + from conan.tools.env.environment import environment_wrap_command + """ Will run a unix command inside a bash terminal It requires to have MSYS2, CYGWIN, or WSL""" + if env: + # Passing env invalidates the conanfile.environment_scripts + env_win = [env] if not isinstance(env, list) else env + env_shell = [] + else: + env_shell = ["conanbuild.sh"] + env_win = ["conanbuild.bat"] + + subsystem = conanfile.conf.get("tools.microsoft.bash:subsystem") + shell_path = conanfile.conf.get("tools.microsoft.bash:path") + + if not platform.system() == "Windows": + raise ConanException("Command only for Windows operating system") + + if not subsystem or not shell_path: + raise ConanException("The config 'tools.microsoft.bash:subsystem' and 'tools.microsoft.bash:path' are " + "needed to run commands in a Windows subsystem") + if subsystem == MSYS2: + # Configure MSYS2 to inherith the PATH + msys2_mode_env = Environment() + _msystem = {"x86": "MINGW32"}.get(conanfile.settings.get_safe("arch"), "MINGW64") + msys2_mode_env.define("MSYSTEM", _msystem) + msys2_mode_env.define("MSYS2_PATH_TYPE", "inherit") + path = os.path.join(conanfile.generators_folder, "msys2_mode.bat") + msys2_mode_env.vars(conanfile, "build").save_bat(path) + env_win.append(path) + + # Needed to change to that dir inside the bash shell + wrapped_shell = '"%s"' % shell_path if " " in shell_path else shell_path + if env_win: + wrapped_shell = environment_wrap_command(env_win, shell_path, + cwd=conanfile.generators_folder) + + cwd = cwd or os.getcwd() + if not os.path.isabs(cwd): + cwd = os.path.join(os.getcwd(), cwd) + cwd_inside = subsystem_path(subsystem, cwd) + wrapped_user_cmd = command + if env_shell: + # Wrapping the inside_command enable to prioritize our environment, otherwise /usr/bin go + # first and there could be commands that we want to skip + wrapped_user_cmd = environment_wrap_command(env_shell, command, + cwd=conanfile.generators_folder) + inside_command = 'cd "{cwd_inside}" && ' \ + '{wrapped_user_cmd}'.format(cwd_inside=cwd_inside, + wrapped_user_cmd=wrapped_user_cmd) + + inside_command = escape_windows_cmd(inside_command) + + final_command = 'cd "{cwd}" && {wrapped_shell} --login -c {inside_command}'.format( + cwd=cwd, + wrapped_shell=wrapped_shell, + inside_command=inside_command) + conanfile.output.info('Running in windows bash: %s' % final_command) + return conanfile._conan_runner(final_command, output=conanfile.output, subprocess=True) + + +def escape_windows_cmd(command): + """ To use in a regular windows cmd.exe + 1. Adds escapes so the argument can be unpacked by CommandLineToArgvW() + 2. Adds escapes for cmd.exe so the argument survives cmd.exe's substitutions. + + Useful to escape commands to be executed in a windows bash (msys2, cygwin etc) + """ + quoted_arg = subprocess.list2cmdline([command]) + return "".join(["^%s" % arg if arg in r'()%!^"<>&|' else arg for arg in quoted_arg]) + + +def deduce_subsystem(conanfile, scope): + if scope.startswith("build"): + if hasattr(conanfile, "settings_build"): + the_os = conanfile.settings_build.get_safe("os") + subsystem = conanfile.settings_build.get_safe("os.subsystem") + else: + the_os = platform.system() # FIXME: Temporary fallback until 2.0 + subsystem = None + else: + the_os = conanfile.settings.get_safe("os") + subsystem = conanfile.settings.get_safe("os.subsystem") + + if not str(the_os).startswith("Windows"): + return None + + if subsystem is None and not scope.startswith("build"): # "run" scope do not follow win_bash + return WINDOWS + + if subsystem is None: # Not defined by settings, so native windows + if not conanfile.win_bash: + return WINDOWS + + subsystem = conanfile.conf.get("tools.microsoft.bash:subsystem") + if not subsystem: + raise ConanException("The config 'tools.microsoft.bash:subsystem' is " + "needed to run commands in a Windows subsystem") + return subsystem + + +def subsystem_path(subsystem, path): + """"Used to translate windows paths to MSYS unix paths like + c/users/path/to/file. Not working in a regular console or MinGW! + """ + if subsystem is None or subsystem == WINDOWS: + return path + + if os.path.exists(path): + # if the path doesn't exist (and abs) we cannot guess the casing + path = get_cased_path(path) + + if path.startswith('\\\\?\\'): + path = path[4:] + path = path.replace(":/", ":\\") + append_prefix = re.match(r'[a-z]:\\', path, re.IGNORECASE) + pattern = re.compile(r'([a-z]):\\', re.IGNORECASE) + path = pattern.sub('/\\1/', path).replace('\\', '/') + + if append_prefix: + if subsystem in (MSYS, MSYS2): + return path.lower() + elif subsystem == CYGWIN: + return '/cygdrive' + path.lower() + elif subsystem == WSL: + return '/mnt' + path[0:2].lower() + path[2:] + elif subsystem == SFU: + path = path.lower() + return '/dev/fs' + path[0] + path[1:].capitalize() + else: + return path if subsystem == WSL else path.lower() + return None + + +def get_cased_path(name): + if platform.system() != "Windows": + return name + if not os.path.isabs(name): + name = os.path.abspath(name) + + result = [] + current = name + while True: + parent, child = os.path.split(current) + if parent == current: + break + + child_cased = child + if os.path.exists(parent): + children = os.listdir(parent) + for c in children: + if c.upper() == child.upper(): + child_cased = c + break + result.append(child_cased) + current = parent + drive, _ = os.path.splitdrive(current) + result.append(drive) + return os.sep.join(reversed(result)) diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py index 84847abc73f..8939a223b99 100644 --- a/conans/model/conan_file.py +++ b/conans/model/conan_file.py @@ -385,7 +385,7 @@ def _run(cmd, _env): return tools.run_in_windows_bash(self, bashcmd=cmd, cwd=cwd, subsystem=subsystem, msys_mingw=msys_mingw, with_login=with_login) elif self.win_bash: # New, Conan 2.0 - from conan.tools.microsoft.subsystems import run_in_windows_bash + from conans.client.subsystems import run_in_windows_bash return run_in_windows_bash(self, command=cmd, cwd=cwd, env=_env) if _env is None: _env = "conanbuild" diff --git a/conans/test/unittests/tools/env/test_env.py b/conans/test/unittests/tools/env/test_env.py index 0eeeef81821..4845b58d2c3 100644 --- a/conans/test/unittests/tools/env/test_env.py +++ b/conans/test/unittests/tools/env/test_env.py @@ -7,7 +7,7 @@ from conan.tools.env import Environment from conan.tools.env.environment import ProfileEnvironment -from conan.tools.microsoft.subsystems import WINDOWS +from conans.client.subsystems import WINDOWS from conans.client.tools import chdir, environment_append from conans.test.utils.mocks import ConanFileMock, MockSettings from conans.test.utils.test_files import temp_folder diff --git a/conans/test/unittests/tools/microsoft/test_subsystem.py b/conans/test/unittests/tools/microsoft/test_subsystem.py new file mode 100644 index 00000000000..07a43d329d1 --- /dev/null +++ b/conans/test/unittests/tools/microsoft/test_subsystem.py @@ -0,0 +1,31 @@ +import textwrap + +import pytest + +from conan.tools.microsoft import unix_path +from conans.model.conf import ConfDefinition +from conans.test.utils.mocks import MockSettings, ConanFileMock + + +@pytest.mark.parametrize("subsystem, expected_path", [ + ("msys2", '/c/path/to/stuff'), + ("msys", '/c/path/to/stuff'), + ("cygwin", '/cygdrive/c/path/to/stuff'), + ("wsl", '/mnt/c/path/to/stuff'), + ("sfu", '/dev/fs/C/path/to/stuff') +]) +def test_unix_path(subsystem, expected_path): + c = ConfDefinition() + c.loads(textwrap.dedent("""\ + tools.microsoft.bash:subsystem={} + """.format(subsystem))) + + settings = MockSettings({"os": "Windows"}) + conanfile = ConanFileMock() + conanfile.conf = c.get_conanfile_conf(None) + conanfile.settings = settings + conanfile.settings_build = settings + conanfile.win_bash = True + + path = unix_path(conanfile, "c:/path/to/stuff") + assert expected_path == path