Skip to content

Commit 09b1d0a

Browse files
Add module partitions and implementation modules.
This commit adds support for implementation partitions and modifies file conventions for a modularized target. The conventions are: - your primary interface unit for your module should always be called 'module.cppm'. It should export module <target-name>. - your interface partitions can have any name supported by a module. For example: MyThings.cppm. The interface partition should declare 'export module <target-name>:MyThings'. - Your *importable* interface implementation units should end with 'Impl.cppm'. For example 'MyThingsImpl.cppm'. This should 'module <target-name>:MyThings' (without export). - Your *non-importable* implementation can have any name *with an extension .cpp*, not a .cppm, since implementation units are not importable. It is highly recommended, though, that if you have a single implementation file, it is called moduleImpl.cpp. You can see the project attached in Github draft PR: #14989 as an example that uses interface partitions, primary interface units, an implementation module (but not an implementation module partition, though it should work), multiple targets and import std. It can run a couple of tests when fully compiled and it resolves dependency order via scanner with Clang.
1 parent d150cb5 commit 09b1d0a

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

mesonbuild/backend/ninjabackend.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .. import compilers
2727
from ..arglist import CompilerArgs
2828
from ..compilers import Compiler
29+
from ..compilers.cpp import CPPCompiler
2930
from ..linkers import ArLikeLinker, RSPFileSyntax
3031
from ..mesonlib import (
3132
File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine,
@@ -1134,12 +1135,12 @@ def should_use_dyndeps_for_target(self, target: 'build.BuildTarget') -> bool:
11341135
if cpp.get_id() == 'clang':
11351136
clang_version_ok = mesonlib.version_compare(cpp.version, '>=17')
11361137
if not clang_version_ok:
1137-
raise MesonException('Tried to compile a library that uses modules. Modules feature is available in clang startint at version 17.')
1138+
raise MesonException('Tried to compile a library that uses modules. Modules feature is available in clang starting at version 17.')
11381139
return (target_has_primary_module_interface or global_scan_enabled) and clang_version_ok and EXPERIMENTAL_CPP_MODULES_FEATURE != 'disabled'
11391140
if cpp.get_id() == 'gcc':
11401141
gcc_version_ok = mesonlib.version_compare(cpp.version, '>=14')
11411142
if not gcc_version_ok:
1142-
raise MesonException('Tried to compile a library that uses modules. Modules feature is available in gcc startint at version 14.')
1143+
raise MesonException('Tried to compile a library that uses modules. Modules feature is available in gcc starting at version 14.')
11431144
return (target_has_primary_module_interface or global_scan_enabled) and clang_version_ok and EXPERIMENTAL_CPP_MODULES_FEATURE != 'disabled'
11441145

11451146
if cpp.get_id() != 'msvc':
@@ -1191,8 +1192,6 @@ def generate_global_dependency_scan_target(self) -> None:
11911192
elem.add_dep(self._all_scan_sources)
11921193
self.add_build(elem)
11931194

1194-
1195-
11961195
# def generate_dependency_scan_target(self, target: build.BuildTarget,
11971196
# compiled_sources: T.List[str],
11981197
# source2object: T.Dict[str, str],
@@ -3166,6 +3165,35 @@ def generate_common_compile_args_per_src_type(self, target: build.BuildTarget) -
31663165
src_type_to_args[src_type_str] = commands.to_native()
31673166
return src_type_to_args
31683167

3168+
def _get_cpp_module_output_name(self, src_basename: str,
3169+
compiler: CPPCompiler,
3170+
target: build.BuildTarget):
3171+
is_module_file = src_basename.endswith('.cppm')
3172+
if not is_module_file:
3173+
# The compiler will not use this output bc it is not a module
3174+
return 'dummy'
3175+
3176+
# Split the filename into root and extension, and take the root part
3177+
src_without_extension = os.path.splitext(src_basename)[0]
3178+
3179+
# The primary interface unit should have the name 'module.cpp' in all cases. It should
3180+
# export as the module name the name of your target. For example, 'hello.world' target
3181+
# 'export module hello.world'
3182+
if src_without_extension == 'module':
3183+
return f"{target.name}{compiler.get_cpp20_module_bmi_extension()}"
3184+
# This is an implementation partition. by convention, it is the name that
3185+
# will be used as the name of the module.
3186+
# For example, given GreetImpl.cppm, then it should declare 'module hello.world:Greet'
3187+
# internally.
3188+
elif (src_without_extension.endswith('Impl')):
3189+
private_partition_name = src_without_extension.split('Impl')[0]
3190+
return f"{target.name}-{private_partition_name}{compiler.get_cpp20_module_bmi_extension()}"
3191+
# This is an interface partition. Same convention as the Impl with the difference that
3192+
# it does not end in 'Impl'. So for a given file 'MySalutation.cppm', the module would do
3193+
# 'export hello.world:MySalutation'
3194+
else:
3195+
return f"{target.name}-{src_without_extension}{compiler.get_cpp20_module_bmi_extension()}"
3196+
31693197
def generate_single_compile(self, target: build.BuildTarget, src,
31703198
is_generated: bool = False, header_deps=None,
31713199
order_deps: T.Optional[T.List[FileOrString]] = None,
@@ -3316,8 +3344,10 @@ def quote_make_target(targetName: str) -> str:
33163344
element.add_item('CUDA_ESCAPED_TARGET', quote_make_target(rel_obj))
33173345

33183346
if self.should_use_dyndeps_for_target(target) and compiler.get_language() == 'cpp' and compiler.get_id() == 'clang':
3347+
src_with_extension = os.path.basename(src.fname)
3348+
mod_output_name = self._get_cpp_module_output_name(src_with_extension, compiler, target)
33193349
commands.extend(['--start-no-unused-arguments',
3320-
f'-fmodule-output={target.name}{compiler.get_cpp20_module_bmi_extension()}',
3350+
f'-fmodule-output={mod_output_name}',
33213351
f'-fprebuilt-module-path={self.environment.get_build_dir()}',
33223352
'--end-no-unused-arguments'])
33233353
element.add_item('ARGS', commands)

mesonbuild/scripts/depscanaccumulate.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ class ModuleProviderInfo:
2121
class CppDependenciesScanner:
2222
pass
2323

24+
def normalize_filename(fname):
25+
return fname.replace(':', '-')
26+
2427
class DynDepRule:
2528
def __init__(self, out: str, imp_outs: T.Optional[T.List[str]], imp_ins: T.List[str]):
2629
self.output = [f'build {out}']
2730
if imp_outs:
28-
imp_out_str = " ".join(imp_outs)
31+
imp_out_str = " ".join([normalize_filename(o) for o in imp_outs])
2932
self.output.append(f" | {imp_out_str}")
3033
self.output.append(": dyndep")
3134
if imp_ins:
32-
imp_ins_str = " ".join(imp_ins)
35+
imp_ins_str = " ".join([normalize_filename(inf) for inf in imp_ins])
3336
self.output.append(" | " + imp_ins_str)
3437
self.output_str = "".join(self.output) + "\n"
3538

0 commit comments

Comments
 (0)