mesonlib: introduce as_posix

Introduce a new function that can be used to join and normalize paths,
and canonicalize them to forward slashes.  It avoids (or rather
hides) use of pathlib for string->string operations.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini authored and Paolo Bonzini committed 2026-09-28 14:30:48 +02:00
1 parent c7fd49a490
commit 7fdfe0eb47
16 files changed
+54 -44

No files matched your search

+5 -5
View File
@@ -27,7 +27,7 @@ from .. import mlog
from .. import compilers
from ..compilers import detect
from ..mesonlib import (
File, MachineChoice, MesonException, MesonBugException, OrderedSet,
as_posix, File, MachineChoice, MesonException, MesonBugException, OrderedSet,
ExecutableSerialisation, FileMode, InstallScriptFailure,
classify_unity_sources, get_compiler_for_source,
get_rsp_threshold, unique_list
@@ -374,16 +374,16 @@ class Backend:
link_lib = target.get_import_filename() or target.get_filename()
# In AIX, if we archive .so, the blibpath must link to archived shared library otherwise to the .so file.
link_lib = self.get_aix_so_archive_name(target, link_lib) or link_lib
return Path(self.get_target_dir(target), link_lib).as_posix()
return as_posix(self.get_target_dir(target), link_lib)
elif isinstance(target, build.StaticLibrary):
return Path(self.get_target_dir(target), target.get_filename()).as_posix()
return as_posix(self.get_target_dir(target), target.get_filename())
elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)):
if not target.is_linkable_target():
raise MesonException(f'Tried to link against custom target "{target.name}", which is not linkable.')
return Path(self.get_target_dir(target), target.get_filename()).as_posix()
return as_posix(self.get_target_dir(target), target.get_filename())
elif isinstance(target, build.Executable):
if target.import_filename:
return Path(self.get_target_dir(target), target.get_import_filename()).as_posix()
return as_posix(self.get_target_dir(target), target.get_import_filename())
else:
return None
raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable')
+2 -2
View File
@@ -33,7 +33,7 @@ from ..mesonlib import (
File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine,
ProgressBar, quote_arg, unique_list
)
from ..mesonlib import get_compiler_for_source, has_path_sep, is_parent_path, lookbehind, path_has_root
from ..mesonlib import as_posix, get_compiler_for_source, has_path_sep, is_parent_path, lookbehind, path_has_root
from ..options import OptionKey
from .backends import CleanTrees
from ..build import GeneratedList, InvalidArguments
@@ -1341,7 +1341,7 @@ class NinjaBackend(backends.Backend):
elem.add_item('DEPFILE', rel_dfile)
if target.console:
elem.add_item('pool', 'console')
full_name = Path(target.subdir, target.name).as_posix()
full_name = as_posix(target.subdir, target.name)
elem.add_item('COMMAND', cmd)
elem.add_item('description', target.description.format(full_name) + cmd_type)
self.add_build(elem)
+4 -4
View File
@@ -21,7 +21,7 @@ from . import mlog
from . import programs
from .environment import MachineMap
from .mesonlib import (
HoldableObject, SecondLevelHolder, SimpleABC, SubProject,
as_posix, HoldableObject, SecondLevelHolder, SimpleABC, SubProject,
File, MesonException, MachineChoice, ThreeMachineChoice, PerMachine,
OrderedSet, classify_unity_sources, ROOT_SUBPROJECT,
get_filenames_templates_dict, substitute_values, has_path_sep,
@@ -776,11 +776,11 @@ class StructuredSources(HoldableObject):
def canonicalize(path: str) -> str:
"""Canonicalize a path within the structure.
The root of the structure is spelled as an empty string.
Like as_posix, but the root of the structure is spelled as an empty string.
"""
if not path:
return ''
path = pathlib.PurePath(os.path.normpath(path)).as_posix()
path = as_posix(path)
return '' if path == '.' else path
def extend(self, path: str, sources: T.Iterable[TargetSources]) -> None:
@@ -1396,7 +1396,7 @@ class BuildTarget(Target, BuildTargetProto):
break
else:
if is_known_suffix(s) and not is_header(s):
path = pathlib.Path(str(s)).as_posix()
path = as_posix(str(s))
m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}'
raise MesonException(m)
+2 -2
View File
@@ -28,7 +28,7 @@ from .manifest import (
validate_patch,
)
from ..mesonlib import (
is_parent_path, lazy_property, MesonException, MachineChoice,
as_posix, is_parent_path, lazy_property, MesonException, MachineChoice,
PerMachine, unique_list, SubProject,
)
from .. import coredata, mlog
@@ -519,7 +519,7 @@ class Interpreter:
if extra_members is not None:
for m in extra_members:
m = PurePath(m).as_posix()
m = as_posix(m)
if m not in workspace.members:
l = ', '.join(sorted(list(workspace.members)))
raise MesonException(f'{m} is not a workspace member for {subdir}/Cargo.toml (valid members are {l})')
+6 -7
View File
@@ -11,11 +11,10 @@ import glob
import os
import re
import typing as T
from pathlib import PurePath
from . import version
from ..mesonlib import MesonException, is_parent_path, lazy_property, MachineChoice
from ..mesonlib import MesonException, as_posix, is_parent_path, lazy_property, MachineChoice
from .. import mlog
if T.TYPE_CHECKING:
@@ -733,7 +732,7 @@ class Workspace:
}
def is_excluded(self, path: str) -> bool:
path = PurePath(path).as_posix()
path = as_posix(path)
if '.' in self.exclude:
# If the workspace directory is excluded, so is everything below it,
# even explicitly listed members (Cargo weirdness), but the root
@@ -755,10 +754,10 @@ class Workspace:
ws.patch = raw.get('patch')
ws.profile = {k: Profile.from_raw(v) for k, v in raw.get('profile', {}).items()}
ws.members = list(PurePath(m).as_posix() for m in ws.members)
ws.exclude = list(PurePath(e).as_posix() for e in ws.exclude)
ws.members = list(as_posix(m) for m in ws.members)
ws.exclude = list(as_posix(e) for e in ws.exclude)
if ws.default_members:
ws.default_members = list(PurePath(m).as_posix() for m in ws.default_members)
ws.default_members = list(as_posix(m) for m in ws.default_members)
else:
ws.default_members = ['.'] if ws.root_package else list(ws.members)
@@ -773,7 +772,7 @@ class Workspace:
continue
if keep_glob_results:
expanded.extend(PurePath(exp).as_posix()
expanded.extend(as_posix(exp)
for exp in glob.glob(entry, root_dir=path)
if os.path.isdir(os.path.join(path, exp)))
return literals, expanded
+2 -2
View File
@@ -25,7 +25,7 @@ from ... import arglist
from ... import mesonlib
from ... import mlog
from ...linkers.linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker
from ...mesonlib import LibType
from ...mesonlib import as_posix, LibType
from .. import compilers
from ..compilers import CompileCheckMode
from .visualstudio import VisualStudioLikeCompiler
@@ -1197,7 +1197,7 @@ class CLikeCompiler(Compiler):
continue
if libname.startswith('lib') and trial_result.startswith(libname) and lib_prefix_warning:
mlog.warning(f'find_library({libname!r}) starting in "lib" only works by accident and is not portable')
return [Path(trial_result).as_posix()]
return [as_posix(trial_result)]
return None
def _find_library_impl(self, libname: str, extra_dirs: T.List[str], code: str, libtype: LibType,
+4 -4
View File
@@ -4,13 +4,13 @@
from __future__ import annotations
from .base import ExternalDependency, DependencyException, sort_libpaths, DependencyTypeName
from ..mesonlib import (EnvironmentVariables, OrderedSet, PerMachine, Popen_safe, Popen_safe_logged, MachineChoice,
join_args, MesonException, path_has_root)
from ..mesonlib import (as_posix, EnvironmentVariables, OrderedSet, PerMachine, Popen_safe, Popen_safe_logged,
MachineChoice, join_args, MesonException, path_has_root)
from ..options import OptionKey
from ..programs import find_external_program, ExternalProgram
from .. import mlog
from enum import Enum
from pathlib import Path, PurePath
from pathlib import PurePath
from functools import lru_cache
import re
import os
@@ -287,7 +287,7 @@ class PkgConfigCLI(PkgConfigInterface):
bpath = self.env.get_build_dir()
if bpath is not None:
# uninstalled can only be used if a build dir exists.
uninstalled_path = Path(bpath, 'meson-uninstalled').as_posix()
uninstalled_path = as_posix(bpath, 'meson-uninstalled')
if uninstalled_path not in extra_paths:
extra_paths.insert(0, uninstalled_path)
env.set('PKG_CONFIG_PATH', extra_paths)
+2 -3
View File
@@ -9,7 +9,6 @@ from __future__ import annotations
import abc
import re
import os
from pathlib import Path
import typing as T
from .base import DependencyException, DependencyMethods
@@ -53,7 +52,7 @@ def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) ->
if len(dirname.split('.')) == 3:
private_dir = dirname
break
return [private_dir, Path(private_dir, f'Qt{module}').as_posix()]
return [private_dir, mesonlib.as_posix(private_dir, f'Qt{module}')]
def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str:
@@ -322,7 +321,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=mesonlib.Simple
modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug)
for module in self.requested_modules:
mincdir = Path(incdir, f'Qt{module}').as_posix()
mincdir = mesonlib.as_posix(incdir, f'Qt{module}')
self.compile_args.append('-I' + mincdir)
if module == 'QuickTest':
+3 -3
View File
@@ -7,7 +7,7 @@ import textwrap
import threading
import sys
from pathlib import Path, PurePath
from pathlib import Path
from .. import mesonlib
from .. import build
@@ -29,7 +29,7 @@ from ..interpreter.type_checking import (
)
from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import Program
from ..mesonlib import File, HoldableObject, listify, MachineChoice, MesonException
from ..mesonlib import as_posix, File, HoldableObject, listify, MachineChoice, MesonException
import typing as T
@@ -891,7 +891,7 @@ class SubprojectHolder(MesonInterpreterObject):
self.warnings = warnings
self.disabled_feature = disabled_feature
self.exception = exception
self.subdir = PurePath(subdir).as_posix()
self.subdir = as_posix(subdir)
self.cm_interpreter: T.Optional[CMakeInterpreter] = None
self.callstack = callstack
+1 -1
View File
@@ -316,7 +316,7 @@ def find_buildsystem_files_list(src_dir: str) -> T.List[str]:
def list_buildsystem_files(coredata: cdata.CoreData, builddata: build.Build, backend: backends.Backend) -> T.List[str]:
src_dir = builddata.environment.get_source_dir()
filelist = [PurePath(src_dir, x).as_posix() for x in builddata.def_files]
filelist = [mesonlib.as_posix(src_dir, x) for x in builddata.def_files]
return filelist
def list_compilers(coredata: cdata.CoreData, builddata: build.Build, backend: backends.Backend) -> T.Dict[str, T.Dict[str, T.Dict[str, str]]]:
+1 -1
View File
@@ -415,7 +415,7 @@ class CmakeModule(ExtensionModule):
abs_install_dir = os.path.join(prefix, install_dir)
# path used in cmake scripts are POSIX even on Windows
PACKAGE_RELATIVE_PATH = pathlib.PurePath(os.path.relpath(prefix, abs_install_dir)).as_posix()
PACKAGE_RELATIVE_PATH = mesonlib.as_posix(prefix, relative_to=abs_install_dir)
extra = ''
if re.match('^(/usr)?/lib(64)?/.+', abs_install_dir):
extra = PACKAGE_INIT_EXT.replace('@absInstallDir@', abs_install_dir)
+1 -2
View File
@@ -8,7 +8,6 @@ from __future__ import annotations
import copy
import itertools
import pathlib
import functools
import os
import subprocess
@@ -1026,7 +1025,7 @@ class GnomeModule(ExtensionModule):
# g-ir-scanner uses distutils/setuptools which splits CC with both Posix
# and Windows rules to get the first argument, which breaks if there are
# backslashes, so best avoid them entirely.
cc_exelist = [pathlib.Path(exe).as_posix() for exe in cc_exelist]
cc_exelist = [mesonlib.as_posix(exe) for exe in cc_exelist]
run_env.set('CC', [quote_arg(x) for x in cc_exelist], ' ')
run_env.set('CFLAGS', [quote_arg(x) for x in env_flags], ' ')
run_env.merge(kwargs['env'])
+2 -2
View File
@@ -16,7 +16,7 @@ import zipfile
from . import mlog
from .ast import IntrospectionInterpreter
from .mesonlib import quiet_git, GitException, Popen_safe, MesonException, windows_proof_rmtree
from .mesonlib import as_posix, quiet_git, GitException, Popen_safe, MesonException, windows_proof_rmtree
from .wrap.wrap import (Resolver, WrapException, WrapType,
parse_patch_url, update_wrap_file, get_releases)
@@ -753,7 +753,7 @@ def run(options: 'Arguments') -> int:
pre_func(options)
logger = Logger(len(wraps))
for wrap in wraps:
dirname = Path(source_dir, subproject_dir, wrap.directory).as_posix()
dirname = as_posix(source_dir, subproject_dir, wrap.directory)
runner = Runner(logger, r, wrap, dirname, options)
task = loop.run_in_executor(executor, runner.run)
tasks.append(task)
+13
View File
@@ -99,6 +99,7 @@ __all__ = [
'RealPathAction',
'TemporaryDirectoryWinProof',
'Version',
'as_posix',
'check_direntry_issues',
'classify_unity_sources',
'current_vs_supports_modules',
@@ -1356,6 +1357,18 @@ def determine_worker_count(varnames: T.Optional[T.List[str]] = None) -> int:
num_workers = 1
return num_workers
def as_posix(*args: str, relative_to: T.Optional[str] = None) -> str:
'''Join and normalize @args into a path that uses forward slashes as the separator,
so that it can be compared with other paths and used as a dictionary key. If
@relative_to is given, the result is relative to that directory.'''
path = os.path.normpath(os.path.join(*args))
if relative_to is not None:
path = os.path.relpath(path, relative_to)
if is_windows():
path = path.replace('\\', '/')
return path
def is_parent_path(parent: str, trial: str) -> bool:
'''Checks if @trial is a file under the directory @parent. Both @trial and @parent should be
adequately normalized, though empty and '.' segments in @parent and @trial are accepted
+4 -4
View File
@@ -26,13 +26,13 @@ import gzip
from base64 import b64encode
from enum import Enum
from netrc import netrc
from pathlib import Path, PurePath
from pathlib import Path
from functools import lru_cache
from . import WrapMode
from .. import coredata
from ..mesonlib import (
DirectoryLock, DirectoryLockAction, quiet_git, GIT, ProgressBar, MesonException,
as_posix, DirectoryLock, DirectoryLockAction, quiet_git, GIT, ProgressBar, MesonException,
windows_proof_rmtree, Popen_safe, SubProject,
)
from ..interpreterbase import FeatureNew
@@ -585,7 +585,7 @@ class Resolver:
with open(main_fname, 'w', encoding='utf-8') as f:
f.write(textwrap.dedent(f'''\
[wrap-redirect]
filename = {PurePath(os.path.relpath(self.wrap.original_filename, self.subdir_root)).as_posix()}
filename = {as_posix(self.wrap.original_filename, relative_to=self.subdir_root)}
'''))
# Map each supported method to a file that must exist at the root of source tree.
@@ -997,7 +997,7 @@ class Resolver:
# Always pass a POSIX path to patch, because on Windows it's MSYS
# Ignore whitespace when applying patches to workaround
# line-ending differences
cmd = [patch_command(), '-l', '-f', '-p1', '-i', str(Path(relpath).as_posix())]
cmd = [patch_command(), '-l', '-f', '-p1', '-i', as_posix(relpath)]
elif GIT:
# If the `patch` command is not available, fall back to `git
# apply`. The `--work-tree` is necessary in case we're inside a
+2 -2
View File
@@ -38,7 +38,7 @@ from mesonbuild.mesonlib import (
is_sunos, windows_proof_rmtree, python_command, version_compare, split_args, quote_arg,
relpath, is_linux, git, search_version, do_conf_file, do_conf_str, default_prefix,
SubProject, MesonException, EnvironmentException,
windows_proof_rm, first
as_posix, windows_proof_rm, first
)
from mesonbuild.options import OptionKey
from mesonbuild.programs import ExternalProgram
@@ -2126,7 +2126,7 @@ class AllPlatformTests(BasePlatformTests):
Version: 1.0.0
Libs: {}
''').format(
Path(linkfile).as_posix().replace(' ', r'\ '),
as_posix(linkfile).replace(' ', r'\ '),
))
# Run the test