Skip to content

Commit 176c6d2

Browse files
committed
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
1 parent 4f1c618 commit 176c6d2

File tree

7 files changed

+53
-49
lines changed

7 files changed

+53
-49
lines changed

mesonbuild/backend/backends.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -824,10 +824,11 @@ def _determine_ext_objs(self, extobj: 'build.ExtractedObjects') -> T.List[str]:
824824
# MSVC generate an object file for PCH
825825
if extobj.pch and self.target_uses_pch(extobj.target):
826826
for lang, pch in extobj.target.pch.items():
827-
compiler = extobj.target.compilers[lang]
828-
if compiler.get_argument_syntax() == 'msvc':
829-
objname = self.get_msvc_pch_objname(lang, pch)
830-
result.append(os.path.join(targetdir, objname))
827+
if pch:
828+
compiler = extobj.target.compilers[lang]
829+
if compiler.get_argument_syntax() == 'msvc':
830+
objname = self.get_msvc_pch_objname(lang, pch)
831+
result.append(os.path.join(targetdir, objname))
831832

832833
# extobj could contain only objects and no sources
833834
if not sources:
@@ -862,13 +863,13 @@ def get_pch_include_args(self, compiler: 'Compiler', target: build.BuildTarget)
862863
args: T.List[str] = []
863864
pchpath = self.get_target_private_dir(target)
864865
includeargs = compiler.get_include_args(pchpath, False)
865-
p = target.get_pch(compiler.get_language())
866+
p = target.pch.get(compiler.get_language())
866867
if p:
867868
args += compiler.get_pch_use_args(pchpath, p[0])
868869
return includeargs + args
869870

870-
def get_msvc_pch_objname(self, lang: str, pch: T.List[str]) -> str:
871-
if len(pch) == 1:
871+
def get_msvc_pch_objname(self, lang: str, pch: T.Tuple[str, T.Optional[str]]) -> str:
872+
if pch[1] is None:
872873
# Same name as in create_msvc_pch_implementation() below.
873874
return f'meson_pch-{lang}.obj'
874875
return os.path.splitext(pch[1])[0] + '.obj'

mesonbuild/backend/ninjabackend.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3180,11 +3180,11 @@ def generate_single_compile(self, target: build.BuildTarget, src,
31803180
# Add MSVC debug file generation compile flags: /Fd /FS
31813181
commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
31823182

3183-
# PCH handling
3184-
if self.target_uses_pch(target):
3185-
pchlist = target.get_pch(compiler.language)
3183+
# PCH handling. We only support PCH for C and C++
3184+
if compiler.language in {'c', 'cpp'} and target.has_pch() and self.target_uses_pch(target):
3185+
pchlist = target.pch[compiler.language]
31863186
else:
3187-
pchlist = []
3187+
pchlist = None
31883188
if not pchlist:
31893189
pch_dep = []
31903190
elif compiler.id == 'intel':
@@ -3328,15 +3328,15 @@ def get_fortran_module_deps(self, target: build.BuildTarget, compiler: Compiler)
33283328
for lt in itertools.chain(target.link_targets, target.link_whole_targets)
33293329
]
33303330

3331-
def generate_msvc_pch_command(self, target, compiler, pch):
3331+
def generate_msvc_pch_command(self, target, compiler, pch: T.Tuple[str, T.Optional[str]]):
33323332
header = pch[0]
33333333
pchname = compiler.get_pch_name(header)
33343334
dst = os.path.join(self.get_target_private_dir(target), pchname)
33353335

33363336
commands = []
33373337
commands += self.generate_basic_compiler_args(target, compiler)
33383338

3339-
if len(pch) == 1:
3339+
if pch[1] is None:
33403340
# Auto generate PCH.
33413341
source = self.create_msvc_pch_implementation(target, compiler.get_language(), pch[0])
33423342
pch_header_dir = os.path.dirname(os.path.join(self.build_to_src, target.get_source_subdir(), header))
@@ -3355,7 +3355,7 @@ def generate_msvc_pch_command(self, target, compiler, pch):
33553355

33563356
return commands, dep, dst, link_objects, source
33573357

3358-
def generate_gcc_pch_command(self, target, compiler, pch):
3358+
def generate_gcc_pch_command(self, target, compiler, pch: str):
33593359
commands = self._generate_single_compile(target, compiler)
33603360
if pch.split('.')[-1] == 'h' and compiler.language == 'cpp':
33613361
# Explicitly compile pch headers as C++. If Clang is invoked in C++ mode, it actually warns if
@@ -3366,21 +3366,21 @@ def generate_gcc_pch_command(self, target, compiler, pch):
33663366
dep = dst + '.' + compiler.get_depfile_suffix()
33673367
return commands, dep, dst, [] # Gcc does not create an object file during pch generation.
33683368

3369-
def generate_mwcc_pch_command(self, target, compiler, pch):
3369+
def generate_mwcc_pch_command(self, target, compiler, pch: str):
33703370
commands = self._generate_single_compile(target, compiler)
33713371
dst = os.path.join(self.get_target_private_dir(target),
33723372
os.path.basename(pch) + '.' + compiler.get_pch_suffix())
33733373
dep = os.path.splitext(dst)[0] + '.' + compiler.get_depfile_suffix()
33743374
return commands, dep, dst, [] # mwcc compilers do not create an object file during pch generation.
33753375

3376-
def generate_pch(self, target, header_deps=None):
3376+
def generate_pch(self, target: build.BuildTarget, header_deps=None):
33773377
header_deps = header_deps if header_deps is not None else []
33783378
pch_objects = []
33793379
for lang in ['c', 'cpp']:
3380-
pch = target.get_pch(lang)
3380+
pch = target.pch[lang]
33813381
if not pch:
33823382
continue
3383-
if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]):
3383+
if not has_path_sep(pch[0]) or (pch[1] and has_path_sep(pch[1])):
33843384
msg = f'Precompiled header of {target.get_basename()!r} must not be in the same ' \
33853385
'directory as source, please put it in a subdirectory.'
33863386
raise InvalidArguments(msg)

mesonbuild/backend/vs2010backend.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -820,23 +820,27 @@ def lang_from_source_file(cls, src):
820820
return 'masm'
821821
raise MesonException(f'Could not guess language from source file {src}.')
822822

823-
def add_pch(self, pch_sources, lang, inc_cl):
823+
def add_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
824+
lang, inc_cl) -> None:
824825
if lang in pch_sources:
825826
self.use_pch(pch_sources, lang, inc_cl)
826827

827-
def create_pch(self, pch_sources, lang, inc_cl):
828+
def create_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
829+
lang, inc_cl) -> None:
828830
pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
829831
pch.text = 'Create'
830832
self.add_pch_files(pch_sources, lang, inc_cl)
831833

832-
def use_pch(self, pch_sources, lang, inc_cl):
834+
def use_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
835+
lang, inc_cl) -> None:
833836
pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
834837
pch.text = 'Use'
835838
header = self.add_pch_files(pch_sources, lang, inc_cl)
836839
pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles')
837840
pch_include.text = header + ';%(ForcedIncludeFiles)'
838841

839-
def add_pch_files(self, pch_sources, lang, inc_cl):
842+
def add_pch_files(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]],
843+
lang, inc_cl) -> str:
840844
header = os.path.basename(pch_sources[lang][0])
841845
pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile')
842846
# When USING PCHs, MSVC will not do the regular include
@@ -1687,25 +1691,25 @@ def path_normalize_add(path, lis):
16871691
else:
16881692
return False
16891693

1690-
pch_sources = {}
1694+
pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]] = {}
16911695
if self.target_uses_pch(target):
16921696
for lang in ['c', 'cpp']:
1693-
pch = target.get_pch(lang)
1697+
pch = target.pch[lang]
16941698
if not pch:
16951699
continue
16961700
if compiler.id == 'msvc':
1697-
if len(pch) == 1:
1701+
if pch[1] is None:
16981702
# Auto generate PCH.
16991703
src = os.path.join(proj_to_build_root, self.create_msvc_pch_implementation(target, lang, pch[0]))
17001704
pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
17011705
else:
17021706
src = os.path.join(proj_to_src_dir, pch[1])
17031707
pch_header_dir = None
1704-
pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
1708+
pch_sources[lang] = (pch[0], src, lang, pch_header_dir)
17051709
else:
17061710
# I don't know whether its relevant but let's handle other compilers
17071711
# used with a vs backend
1708-
pch_sources[lang] = [pch[0], None, lang, None]
1712+
pch_sources[lang] = (pch[0], None, lang, None)
17091713

17101714
previous_includes = []
17111715
if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:

mesonbuild/backend/xcodebackend.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,9 +1802,7 @@ def generate_single_build_target(self, objects_dict: PbxDict, target_name: str,
18021802
# Xcode uses GCC_PREFIX_HEADER which only allows one file per target/executable. Precompiling various header files and
18031803
# applying a particular pch to each source file will require custom scripts (as a build phase) and build flags per each
18041804
# file. Since Xcode itself already discourages precompiled headers in favor of modules we don't try much harder here.
1805-
pchs = target.get_pch('c') + target.get_pch('cpp') + target.get_pch('objc') + target.get_pch('objcpp')
1806-
# Make sure to use headers (other backends require implementation files like *.c *.cpp, etc; these should not be used here)
1807-
pchs = [pch for pch in pchs if pch.endswith('.h') or pch.endswith('.hh') or pch.endswith('hpp')]
1805+
pchs = [t[0] for t in [target.pch['c'], target.pch['cpp']] if t is not None]
18081806
if pchs:
18091807
if len(pchs) > 1:
18101808
mlog.warning(f'Unsupported Xcode configuration: More than 1 precompiled header found "{pchs!s}". Target "{target.name}" might not compile correctly.')

mesonbuild/build.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ class BuildTargetKeywordArguments(TypedDict, total=False):
7070

7171
build_by_default: bool
7272
build_rpath: str
73-
c_pch: T.List[str]
74-
cpp_pch: T.List[str]
73+
c_pch: T.Optional[T.Tuple[str, T.Optional[str]]]
74+
cpp_pch: T.Optional[T.Tuple[str, T.Optional[str]]]
7575
d_debug: T.List[T.Union[str, int]]
7676
d_import_dirs: T.List[IncludeDirs]
7777
d_module_versions: T.List[T.Union[str, int]]
@@ -807,7 +807,7 @@ def __init__(
807807
# The list of all files outputted by this target. Useful in cases such
808808
# as Vala which generates .vapi and .h besides the compiled output.
809809
self.outputs = [self.filename]
810-
self.pch: T.Dict[str, T.List[str]] = {}
810+
self.pch: T.Dict[str, T.Optional[T.Tuple[str, T.Optional[str]]]] = {}
811811
self.extra_args: T.DefaultDict[str, T.List[str]] = kwargs.get('language_args', defaultdict(list))
812812
self.sources: T.List[File] = []
813813
# If the same source is defined multiple times, use it only once.
@@ -1266,8 +1266,8 @@ def process_kwargs(self, kwargs: BuildTargetKeywordArguments) -> None:
12661266

12671267
self.raw_overrides = kwargs.get('override_options', {})
12681268

1269-
self.add_pch('c', kwargs.get('c_pch', []))
1270-
self.add_pch('cpp', kwargs.get('cpp_pch', []))
1269+
self.pch['c'] = kwargs.get('c_pch')
1270+
self.pch['cpp'] = kwargs.get('cpp_pch')
12711271

12721272
self.link_args = extract_as_list(kwargs, 'link_args')
12731273
for i in self.link_args:
@@ -1416,10 +1416,7 @@ def should_install(self) -> bool:
14161416
return self.install
14171417

14181418
def has_pch(self) -> bool:
1419-
return bool(self.pch)
1420-
1421-
def get_pch(self, language: str) -> T.List[str]:
1422-
return self.pch.get(language, [])
1419+
return any(x is not None for x in self.pch.values())
14231420

14241421
def get_include_dirs(self) -> T.List['IncludeDirs']:
14251422
return self.include_dirs
@@ -1590,10 +1587,6 @@ def check_can_link_together(self, t: BuildTargetTypes) -> None:
15901587
else:
15911588
mlog.warning(msg + ' This will fail in cross build.')
15921589

1593-
def add_pch(self, language: str, pchlist: T.List[str]) -> None:
1594-
if pchlist:
1595-
self.pch[language] = pchlist
1596-
15971590
def add_include_dirs(self, args: T.Sequence['IncludeDirs'], set_is_system: T.Optional[str] = None) -> None:
15981591
ids: T.List['IncludeDirs'] = []
15991592
for a in args:

mesonbuild/interpreter/interpreter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3479,7 +3479,7 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs
34793479
self.check_for_jar_sources(sources, targetclass)
34803480
kwargs['d_import_dirs'] = self.extract_incdirs(kwargs, 'd_import_dirs')
34813481
missing: T.List[str] = []
3482-
for each in itertools.chain(kwargs['c_pch'], kwargs['cpp_pch']):
3482+
for each in itertools.chain(kwargs['c_pch'] or [], kwargs['cpp_pch'] or []):
34833483
if each is not None:
34843484
if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, each)):
34853485
missing.append(os.path.join(self.subdir, each))

mesonbuild/interpreter/type_checking.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -679,11 +679,19 @@ def _pch_feature_validator(args: T.List[str]) -> T.Iterable[FeatureCheckBase]:
679679
yield FeatureDeprecated('PCH source files', '0.50.0', 'Only a single header file should be used.')
680680

681681

682-
def _pch_convertor(args: T.List[str]) -> T.List[str]:
683-
# Flip so that we always have [header, src]
684-
if len(args) == 2 and compilers.is_source(args[0]):
685-
return [args[1], args[0]]
686-
return args
682+
def _pch_convertor(args: T.List[str]) -> T.Optional[T.Tuple[str, T.Optional[str]]]:
683+
num_args = len(args)
684+
685+
if num_args == 1:
686+
return (args[0], None)
687+
688+
if num_args == 2:
689+
if compilers.is_source(args[0]):
690+
# Flip so that we always have [header, src]
691+
return (args[1], args[0])
692+
return (args[0], args[1])
693+
694+
return None
687695

688696

689697
_PCH_ARGS: KwargInfo[T.List[str]] = KwargInfo(

0 commit comments

Comments
 (0)