From 22526d318efeafe4aa8d7e6c2f4a1ef9eb3028af Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:09:51 +0900 Subject: [PATCH 1/2] Convert configure command path and prefix to unix style on windows On Cygwin, MSYS2 and GitBash, the configure command and the prefix should be converted to unix style path by cygpath command, because the colon in the drive letter breaks many configure scripts. This also fixes bugs of previous approach where leading slash was missing from prefix and it sometimes use wrong directory as base path. In addition removing drive letter should be done on rel_prefix only. --- .../external_project-cygpath-conversion.md | 7 +++++ mesonbuild/modules/external_project.py | 27 ++++++++++++++----- .../230 external project/libfoo/configure | 17 ++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 docs/markdown/snippets/external_project-cygpath-conversion.md 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/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 From 83aad4185ab374b275bc8cc391f8cf242d2a153c Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sat, 8 Feb 2025 19:44:17 +0900 Subject: [PATCH 2/2] Enable test case common/230 on msys2 Implemented 'windows' and '!windows' as platform value in test configuration for this. In MSVC environment this test is ignored anyway since make command is not available. --- data/test.schema.json | 4 +++- docs/markdown/Contributing.md | 2 ++ run_project_tests.py | 2 ++ test cases/common/230 external project/libfoo/meson.build | 1 + test cases/common/230 external project/meson.build | 4 ---- test cases/common/230 external project/test.json | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) 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/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/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" } ]