diff --git a/data/test.schema.json b/data/test.schema.json index e87e7d03a9e5..1fe60a694c14 100644 --- a/data/test.schema.json +++ b/data/test.schema.json @@ -43,7 +43,9 @@ "msvc", "gcc", "cygwin", - "!cygwin" + "!cygwin", + "windows", + "!windows" ] }, "version": { diff --git a/docs/markdown/Contributing.md b/docs/markdown/Contributing.md index 729dabf5cca0..0baad7896d11 100644 --- a/docs/markdown/Contributing.md +++ b/docs/markdown/Contributing.md @@ -320,6 +320,8 @@ considered if the platform matches. The following values for | `gcc` | Not `msvc` | | `cygwin` | Matches when the platform is cygwin | | `!cygwin` | Not `cygwin` | +| `windows` | Matches when the platform is windows or cygwin | +| `!windows` | Not `windows` | #### matrix 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 000000000000..fe7178726fd2 --- /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 ba7b3007597d..4011577014e4 100644 --- a/mesonbuild/modules/external_project.py +++ b/mesonbuild/modules/external_project.py @@ -6,6 +6,7 @@ from pathlib import Path import os import shlex +import shutil import subprocess import typing as T @@ -19,7 +20,7 @@ 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 @@ def __init__(self, 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 @@ def _configure(self, state: 'ModuleState') -> None: 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/run_project_tests.py b/run_project_tests.py index ce6e5c2da4f8..37037d9a6a5b 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -158,6 +158,8 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa 'gcc': canonical_compiler != 'msvc', 'cygwin': env.machines.host.is_cygwin(), '!cygwin': not env.machines.host.is_cygwin(), + 'windows': env.machines.host.is_windows() or env.machines.host.is_cygwin(), + '!windows': not (env.machines.host.is_windows() or env.machines.host.is_cygwin()), }.get(self.platform or '', True) if not matches: return None diff --git a/test cases/common/230 external project/libfoo/configure b/test cases/common/230 external project/libfoo/configure index 0e4aa72b22b9..853b6f173dc9 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 diff --git a/test cases/common/230 external project/libfoo/meson.build b/test cases/common/230 external project/libfoo/meson.build index a2512aa6fddb..f69600c6e5b0 100644 --- a/test cases/common/230 external project/libfoo/meson.build +++ b/test cases/common/230 external project/libfoo/meson.build @@ -17,6 +17,7 @@ p = mod.add_project('configure', '--libext=' + libext, ], depends: somelib, + verbose: true, ) libfoo_dep = declare_dependency(link_with : somelib, diff --git a/test cases/common/230 external project/meson.build b/test cases/common/230 external project/meson.build index d1ed797c4af8..386e52561539 100644 --- a/test cases/common/230 external project/meson.build +++ b/test cases/common/230 external project/meson.build @@ -8,10 +8,6 @@ if not find_program('make', required : false).found() error('MESON_SKIP_TEST: make not found') endif -if host_machine.system() == 'windows' - error('MESON_SKIP_TEST: The fake configure script is too dumb to work on Windows') -endif - if meson.is_cross_build() # CI uses PKG_CONFIG_SYSROOT_DIR which breaks -uninstalled.pc usage. error('MESON_SKIP_TEST: Cross build support is too limited for this test') diff --git a/test cases/common/230 external project/test.json b/test cases/common/230 external project/test.json index 4df7d4ac57ff..ff8b55838647 100644 --- a/test cases/common/230 external project/test.json +++ b/test cases/common/230 external project/test.json @@ -1,7 +1,7 @@ { "installed": [ - { "type": "shared_lib", "file": "usr/lib/foo", "platform": "!cygwin" }, - { "type": "file", "file": "usr/lib/libfoo.dll", "platform": "cygwin" }, + { "type": "shared_lib", "file": "usr/lib/foo", "platform": "!windows" }, + { "type": "file", "file": "usr/lib/libfoo.dll", "platform": "windows" }, { "type": "file", "file": "usr/include/libfoo.h" }, { "type": "file", "file": "usr/lib/pkgconfig/somelib.pc" } ]