diff --git a/docs/markdown/snippets/external_project-cygpath-conversion.md b/docs/markdown/snippets/external_project-cygpath-conversion.md new file mode 100644 index 000000000..fe7178726 --- /dev/null +++ b/docs/markdown/snippets/external_project-cygpath-conversion.md @@ -0,0 +1,7 @@ +## The external_project module uses the cygpath command to convert paths + +In previous versions, the external_project module on Windows used a Windows-style path (e.g., `C:/path/to/configure`) to execute the configure file, and a relative path from the drive root (e.g., `/path/to/prefix`) as the installation prefix. +However, since configure scripts are typically intended to be run in a POSIX-like environment (MSYS2, Cygwin, or GitBash), these paths were incompatible with some configure scripts. + +The external_project module now uses the `cygpath` command to convert the configure command path and prefix to Unix-style paths (e.g., `/c/path/to/configure` for MSYS2 and `/cygdrive/c/path/to/configure` for Cygwin). +If the `cygpath` command is not found in the PATH, it will fall back to the previous behavior. diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py index 397cb71b3..e8d48ceea 100644 --- a/mesonbuild/modules/external_project.py +++ b/mesonbuild/modules/external_project.py @@ -6,6 +6,7 @@ from __future__ import annotations from pathlib import Path import os import shlex +import shutil import subprocess import typing as T @@ -19,7 +20,7 @@ from ..interpreterbase import FeatureNew from ..interpreter.type_checking import ENV_KW, DEPENDS_KW from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args from ..mesonlib import (EnvironmentException, MesonException, Popen_safe, MachineChoice, - get_variable_regex, do_replacement, join_args, relpath) + get_variable_regex, do_replacement, join_args) from ..options import OptionKey if T.TYPE_CHECKING: @@ -89,19 +90,29 @@ class ExternalProject(NewExtensionModule): self.includedir = Path(_i) self.name = self.src_dir.name - # On Windows if the prefix is "c:/foo" and DESTDIR is "c:/bar", `make` - # will install files into "c:/bar/c:/foo" which is an invalid path. - # Work around that issue by removing the drive from prefix. - if self.prefix.drive: - self.prefix = Path(relpath(self.prefix, self.prefix.drive)) + self.prefix = self._cygpath_convert(self.prefix) # self.prefix is an absolute path, so we cannot append it to another path. - self.rel_prefix = Path(relpath(self.prefix, self.prefix.root)) + # On Windows (where cygpath is not applied), + # if the prefix is "c:/foo" and DESTDIR is "c:/bar", + # `make` will install files into "c:/bar/c:/foo" which is an invalid path. + # This also removes the drive letter from the prefix to workaround the issue. + self.rel_prefix = self.prefix.relative_to(self.prefix.anchor) self._configure(state) self.targets = self._create_targets(extra_depends) + def _cygpath_convert(self, winpath: Path) -> Path: + # On Cygwin, MSYS2 and GitBash, the configure command and the prefix + # should be converted to unix style path like "/c/foo" by cygpath command, + # because the colon in the drive letter breaks many configure scripts. + # Do nothing on other environment where cygpath is not available. + if winpath.drive and shutil.which('cygpath'): + _p, o, _e = Popen_safe(['cygpath', '-u', winpath.as_posix()]) + return Path(o.strip('\n')) + return winpath + def _configure(self, state: 'ModuleState') -> None: if self.configure_command == 'waf': FeatureNew('Waf external project', '0.60.0').use(self.subproject, state.current_node) @@ -116,6 +127,8 @@ class ExternalProject(NewExtensionModule): configure_path = Path(self.src_dir, self.configure_command) configure_prog = state.find_program(configure_path.as_posix()) configure_cmd = configure_prog.get_command() + if len(configure_cmd) >= 2 and configure_cmd[-1] == configure_path.as_posix(): + configure_cmd = configure_cmd[:-1] + [self._cygpath_convert(configure_path).as_posix()] workdir = self.build_dir self.make = state.find_program('make').get_command() diff --git a/test cases/common/230 external project/libfoo/configure b/test cases/common/230 external project/libfoo/configure index 0e4aa72b2..853b6f173 100755 --- a/test cases/common/230 external project/libfoo/configure +++ b/test cases/common/230 external project/libfoo/configure @@ -2,6 +2,23 @@ srcdir=$(dirname "$0") +# some configure scripts seem to iterate over srcdir and other paths +# with for-loop using path_separator (most cases colon.) +PATH_SEPARATOR=: +(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { +(PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' +} +IFS=$PATH_SEPARATOR +for i in $srcdir +do +if [ "$i" != "$srcdir" ] +then + echo "failed to extract $srcdir using path separator $PATH_SEPARATOR" + exit 1 +fi +done + for i in "$@" do case $i in