summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2025-11-03 11:04:35 -0800
committerDylan Baker <dylan@pnwbakers.com>2025-11-12 08:14:37 -0800
commit176c6d27e3e84f884df42cff6813cc5ca77f91c8 (patch)
tree04ce87d3666b01ec8d949e5d4c3d0d0446ee51e2
parent4f1c618392972c935d83eb7939234b3b90479df5 (diff)
downloadmeson-176c6d27e3e84f884df42cff6813cc5ca77f91c8.tar.gz
build: Use a tuple for pch data
This really isn't a list because it's not homogenous data, it's really `tuple[str, str | None] | None`, but we're using list length to decide what to do with it, and that makes for strict null issues, as an accurate annotation would be `list[str | None]`, which would require a lot of `is not None` checking. By using a tuple we don't need to keep checking length, which is more expensive than null checking. To ensure correctness I annotated some things in the VS backend
-rw-r--r--mesonbuild/backend/backends.py15
-rw-r--r--mesonbuild/backend/ninjabackend.py22
-rw-r--r--mesonbuild/backend/vs2010backend.py22
-rw-r--r--mesonbuild/backend/xcodebackend.py4
-rw-r--r--mesonbuild/build.py19
-rw-r--r--mesonbuild/interpreter/interpreter.py2
-rw-r--r--mesonbuild/interpreter/type_checking.py18
7 files changed, 53 insertions, 49 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 76bd20932..7fba3f3fb 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -824,10 +824,11 @@ class Backend:
# MSVC generate an object file for PCH
if extobj.pch and self.target_uses_pch(extobj.target):
for lang, pch in extobj.target.pch.items():
- compiler = extobj.target.compilers[lang]
- if compiler.get_argument_syntax() == 'msvc':
- objname = self.get_msvc_pch_objname(lang, pch)
- result.append(os.path.join(targetdir, objname))
+ if pch:
+ compiler = extobj.target.compilers[lang]
+ if compiler.get_argument_syntax() == 'msvc':
+ objname = self.get_msvc_pch_objname(lang, pch)
+ result.append(os.path.join(targetdir, objname))
# extobj could contain only objects and no sources
if not sources:
@@ -862,13 +863,13 @@ class Backend:
args: T.List[str] = []
pchpath = self.get_target_private_dir(target)
includeargs = compiler.get_include_args(pchpath, False)
- p = target.get_pch(compiler.get_language())
+ p = target.pch.get(compiler.get_language())
if p:
args += compiler.get_pch_use_args(pchpath, p[0])
return includeargs + args
- def get_msvc_pch_objname(self, lang: str, pch: T.List[str]) -> str:
- if len(pch) == 1:
+ def get_msvc_pch_objname(self, lang: str, pch: T.Tuple[str, T.Optional[str]]) -> str:
+ if pch[1] is None:
# Same name as in create_msvc_pch_implementation() below.
return f'meson_pch-{lang}.obj'
return os.path.splitext(pch[1])[0] + '.obj'
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 3510d19b4..90c61a26b 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -3180,11 +3180,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
# Add MSVC debug file generation compile flags: /Fd /FS
commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
- # PCH handling
- if self.target_uses_pch(target):
- pchlist = target.get_pch(compiler.language)
+ # PCH handling. We only support PCH for C and C++
+ if compiler.language in {'c', 'cpp'} and target.has_pch() and self.target_uses_pch(target):
+ pchlist = target.pch[compiler.language]
else:
- pchlist = []
+ pchlist = None
if not pchlist:
pch_dep = []
elif compiler.id == 'intel':
@@ -3328,7 +3328,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
for lt in itertools.chain(target.link_targets, target.link_whole_targets)
]
- def generate_msvc_pch_command(self, target, compiler, pch):
+ def generate_msvc_pch_command(self, target, compiler, pch: T.Tuple[str, T.Optional[str]]):
header = pch[0]
pchname = compiler.get_pch_name(header)
dst = os.path.join(self.get_target_private_dir(target), pchname)
@@ -3336,7 +3336,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
commands = []
commands += self.generate_basic_compiler_args(target, compiler)
- if len(pch) == 1:
+ if pch[1] is None:
# Auto generate PCH.
source = self.create_msvc_pch_implementation(target, compiler.get_language(), pch[0])
pch_header_dir = os.path.dirname(os.path.join(self.build_to_src, target.get_source_subdir(), header))
@@ -3355,7 +3355,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
return commands, dep, dst, link_objects, source
- def generate_gcc_pch_command(self, target, compiler, pch):
+ def generate_gcc_pch_command(self, target, compiler, pch: str):
commands = self._generate_single_compile(target, compiler)
if pch.split('.')[-1] == 'h' and compiler.language == 'cpp':
# Explicitly compile pch headers as C++. If Clang is invoked in C++ mode, it actually warns if
@@ -3366,21 +3366,21 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
dep = dst + '.' + compiler.get_depfile_suffix()
return commands, dep, dst, [] # Gcc does not create an object file during pch generation.
- def generate_mwcc_pch_command(self, target, compiler, pch):
+ def generate_mwcc_pch_command(self, target, compiler, pch: str):
commands = self._generate_single_compile(target, compiler)
dst = os.path.join(self.get_target_private_dir(target),
os.path.basename(pch) + '.' + compiler.get_pch_suffix())
dep = os.path.splitext(dst)[0] + '.' + compiler.get_depfile_suffix()
return commands, dep, dst, [] # mwcc compilers do not create an object file during pch generation.
- def generate_pch(self, target, header_deps=None):
+ def generate_pch(self, target: build.BuildTarget, header_deps=None):
header_deps = header_deps if header_deps is not None else []
pch_objects = []
for lang in ['c', 'cpp']:
- pch = target.get_pch(lang)
+ pch = target.pch[lang]
if not pch:
continue
- if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]):
+ if not has_path_sep(pch[0]) or (pch[1] and has_path_sep(pch[1])):
msg = f'Precompiled header of {target.get_basename()!r} must not be in the same ' \
'directory as source, please put it in a subdirectory.'
raise InvalidArguments(msg)
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index 9c9f61259..1753ef002 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -820,23 +820,27 @@ class Vs2010Backend(backends.Backend):
return 'masm'
raise MesonException(f'Could not guess language from source file {src}.')
- def add_pch(self, pch_sources, lang, inc_cl):
+ def add_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
+ lang, inc_cl) -> None:
if lang in pch_sources:
self.use_pch(pch_sources, lang, inc_cl)
- def create_pch(self, pch_sources, lang, inc_cl):
+ def create_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
+ lang, inc_cl) -> None:
pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
pch.text = 'Create'
self.add_pch_files(pch_sources, lang, inc_cl)
- def use_pch(self, pch_sources, lang, inc_cl):
+ def use_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
+ lang, inc_cl) -> None:
pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
pch.text = 'Use'
header = self.add_pch_files(pch_sources, lang, inc_cl)
pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles')
pch_include.text = header + ';%(ForcedIncludeFiles)'
- def add_pch_files(self, pch_sources, lang, inc_cl):
+ def add_pch_files(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
+ lang, inc_cl) -> str:
header = os.path.basename(pch_sources[lang][0])
pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile')
# When USING PCHs, MSVC will not do the regular include
@@ -1687,25 +1691,25 @@ class Vs2010Backend(backends.Backend):
else:
return False
- pch_sources = {}
+ pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]] = {}
if self.target_uses_pch(target):
for lang in ['c', 'cpp']:
- pch = target.get_pch(lang)
+ pch = target.pch[lang]
if not pch:
continue
if compiler.id == 'msvc':
- if len(pch) == 1:
+ if pch[1] is None:
# Auto generate PCH.
src = os.path.join(proj_to_build_root, self.create_msvc_pch_implementation(target, lang, pch[0]))
pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
else:
src = os.path.join(proj_to_src_dir, pch[1])
pch_header_dir = None
- pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
+ pch_sources[lang] = (pch[0], src, lang, pch_header_dir)
else:
# I don't know whether its relevant but let's handle other compilers
# used with a vs backend
- pch_sources[lang] = [pch[0], None, lang, None]
+ pch_sources[lang] = (pch[0], None, lang, None)
previous_includes = []
if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index 87e1309a6..95a62a02a 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -1802,9 +1802,7 @@ class XCodeBackend(backends.Backend):
# Xcode uses GCC_PREFIX_HEADER which only allows one file per target/executable. Precompiling various header files and
# applying a particular pch to each source file will require custom scripts (as a build phase) and build flags per each
# file. Since Xcode itself already discourages precompiled headers in favor of modules we don't try much harder here.
- pchs = target.get_pch('c') + target.get_pch('cpp') + target.get_pch('objc') + target.get_pch('objcpp')
- # Make sure to use headers (other backends require implementation files like *.c *.cpp, etc; these should not be used here)
- pchs = [pch for pch in pchs if pch.endswith('.h') or pch.endswith('.hh') or pch.endswith('hpp')]
+ pchs = [t[0] for t in [target.pch['c'], target.pch['cpp']] if t is not None]
if pchs:
if len(pchs) > 1:
mlog.warning(f'Unsupported Xcode configuration: More than 1 precompiled header found "{pchs!s}". Target "{target.name}" might not compile correctly.')
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index c2b218955..4721df113 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -70,8 +70,8 @@ if T.TYPE_CHECKING:
build_by_default: bool
build_rpath: str
- c_pch: T.List[str]
- cpp_pch: T.List[str]
+ c_pch: T.Optional[T.Tuple[str, T.Optional[str]]]
+ cpp_pch: T.Optional[T.Tuple[str, T.Optional[str]]]
d_debug: T.List[T.Union[str, int]]
d_import_dirs: T.List[IncludeDirs]
d_module_versions: T.List[T.Union[str, int]]
@@ -807,7 +807,7 @@ class BuildTarget(Target):
# The list of all files outputted by this target. Useful in cases such
# as Vala which generates .vapi and .h besides the compiled output.
self.outputs = [self.filename]
- self.pch: T.Dict[str, T.List[str]] = {}
+ self.pch: T.Dict[str, T.Optional[T.Tuple[str, T.Optional[str]]]] = {}
self.extra_args: T.DefaultDict[str, T.List[str]] = kwargs.get('language_args', defaultdict(list))
self.sources: T.List[File] = []
# If the same source is defined multiple times, use it only once.
@@ -1266,8 +1266,8 @@ class BuildTarget(Target):
self.raw_overrides = kwargs.get('override_options', {})
- self.add_pch('c', kwargs.get('c_pch', []))
- self.add_pch('cpp', kwargs.get('cpp_pch', []))
+ self.pch['c'] = kwargs.get('c_pch')
+ self.pch['cpp'] = kwargs.get('cpp_pch')
self.link_args = extract_as_list(kwargs, 'link_args')
for i in self.link_args:
@@ -1416,10 +1416,7 @@ class BuildTarget(Target):
return self.install
def has_pch(self) -> bool:
- return bool(self.pch)
-
- def get_pch(self, language: str) -> T.List[str]:
- return self.pch.get(language, [])
+ return any(x is not None for x in self.pch.values())
def get_include_dirs(self) -> T.List['IncludeDirs']:
return self.include_dirs
@@ -1590,10 +1587,6 @@ class BuildTarget(Target):
else:
mlog.warning(msg + ' This will fail in cross build.')
- def add_pch(self, language: str, pchlist: T.List[str]) -> None:
- if pchlist:
- self.pch[language] = pchlist
-
def add_include_dirs(self, args: T.Sequence['IncludeDirs'], set_is_system: T.Optional[str] = None) -> None:
ids: T.List['IncludeDirs'] = []
for a in args:
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 36647f32c..a3cdde04a 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -3479,7 +3479,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.check_for_jar_sources(sources, targetclass)
kwargs['d_import_dirs'] = self.extract_incdirs(kwargs, 'd_import_dirs')
missing: T.List[str] = []
- for each in itertools.chain(kwargs['c_pch'], kwargs['cpp_pch']):
+ for each in itertools.chain(kwargs['c_pch'] or [], kwargs['cpp_pch'] or []):
if each is not None:
if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, each)):
missing.append(os.path.join(self.subdir, each))
diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py
index 5dc9127f3..6cd43b7c6 100644
--- a/mesonbuild/interpreter/type_checking.py
+++ b/mesonbuild/interpreter/type_checking.py
@@ -679,11 +679,19 @@ def _pch_feature_validator(args: T.List[str]) -> T.Iterable[FeatureCheckBase]:
yield FeatureDeprecated('PCH source files', '0.50.0', 'Only a single header file should be used.')
-def _pch_convertor(args: T.List[str]) -> T.List[str]:
- # Flip so that we always have [header, src]
- if len(args) == 2 and compilers.is_source(args[0]):
- return [args[1], args[0]]
- return args
+def _pch_convertor(args: T.List[str]) -> T.Optional[T.Tuple[str, T.Optional[str]]]:
+ num_args = len(args)
+
+ if num_args == 1:
+ return (args[0], None)
+
+ if num_args == 2:
+ if compilers.is_source(args[0]):
+ # Flip so that we always have [header, src]
+ return (args[1], args[0])
+ return (args[0], args[1])
+
+ return None
_PCH_ARGS: KwargInfo[T.List[str]] = KwargInfo(