Skip to content

Commit b5c9469

Browse files
Add 'import std' support for Meson
This commit adds the ability for Meson targets for clang (gcc coming soon) to use 'import std'. The feature can be used by setting a new option 'cpp_import_std' globally, which can be true or false. By default the option is conservatively set to false. If enabled, during the configuration phase, Meson checks that the compiler supports building the std library as a module via compiler version checks. At build time, it adds a custom target to build the std bmi. This bmi is wrapped in a dependency. When build_target is invoked, if it uses cpp_import_std feature and the target uses C++, the dependency is added to the target (see build_target function). A best effort has been done to align debug/release flags with the built version of the std library, though this needs review. Note that in order to use the feature, the minimum version is C++23, since import std is a C++23 feature. An example of how to use it can be found in test '1 import std'. Smoke-tested in: - Clang 19 homebrew, in my Mac. ROADMAP: - Add gcc support. - Add override to build targets to avoid using 'import std'.
1 parent 75ef081 commit b5c9469

File tree

9 files changed

+226
-2
lines changed

9 files changed

+226
-2
lines changed

mesonbuild/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,6 +1704,9 @@ def get_used_stdlib_args(self, link_language: str) -> T.List[str]:
17041704
stdlib_args.extend(all_compilers[dl].language_stdlib_only_link_flags(self.environment))
17051705
return stdlib_args
17061706

1707+
def uses_cpp(self) -> bool:
1708+
return 'cpp' in self.compilers
1709+
17071710
def uses_rust(self) -> bool:
17081711
return 'rust' in self.compilers
17091712

mesonbuild/compilers/cpp.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
from __future__ import annotations
55

66
import functools
7+
import os
78
import os.path
89
import typing as T
910

1011
from .. import options
1112
from .. import mlog
12-
from ..mesonlib import MesonException, version_compare
13+
from ..mesonlib import (File, MesonException, Popen_safe_logged,
14+
version_compare)
1315

1416
from .compilers import (
1517
gnu_winlibs,
1618
msvc_winlibs,
1719
Compiler,
1820
CompileCheckMode,
21+
CompileResult
1922
)
2023
from .c_function_attributes import CXX_FUNC_ATTRIBUTES, C_FUNC_ATTRIBUTES
2124
from .mixins.apple import AppleCompilerMixin, AppleCPPStdsMixin
@@ -88,7 +91,57 @@ def get_no_stdlib_link_args(self) -> T.List[str]:
8891

8992
def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
9093
code = 'class breakCCompiler;int main(void) { return 0; }\n'
91-
return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
94+
self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
95+
if environment.coredata.optstore.get_value('cpp_import_std'):
96+
self._import_cpp_std_sanity_check(work_dir, environment)
97+
98+
def compile_import_std_module(self,
99+
env: 'Environment',
100+
code: File):
101+
cpp_std = env.coredata.optstore.get_value('cpp_std')
102+
srcname = code.fname
103+
# Construct the compiler command-line
104+
commands = self.compiler_args()
105+
commands.append(f"-std={cpp_std}")
106+
commands.extend(['-Wno-reserved-identifier', '-Wno-reserved-module-identifier'])
107+
commands.append("--precompile")
108+
109+
all_lists_to_add = [self.get_always_args(), self.get_debug_args(env.coredata.optstore.get_value('buildtype') == 'debug'),
110+
self.get_assert_args(disable=env.coredata.optstore.get_value('b_ndebug') in ['if-release', 'true'],
111+
env=env)]
112+
for args_list in all_lists_to_add:
113+
for arg in args_list:
114+
commands.append(arg)
115+
commands.append(srcname)
116+
tmpdirname = env.build_dir
117+
118+
# Preprocess mode outputs to stdout, so no output args
119+
print(f"***{self.get_exelist()}")
120+
output = f'std{self.get_cpp20_module_bmi_extension()}'
121+
commands += self.get_output_args(output)
122+
no_ccache = True
123+
os_env = os.environ.copy()
124+
os_env['LC_ALL'] = 'C'
125+
os_env['CCACHE_DISABLE'] = '1'
126+
command_list = self.get_exelist(ccache=not no_ccache) + commands.to_native()
127+
p, stdo, stde = Popen_safe_logged(command_list, msg="Command line for compiling 'import std' feature", cwd=tmpdirname, env=os_env)
128+
if p.returncode != 0:
129+
raise MesonException("Could not compile library for use with 'import std'")
130+
131+
def get_import_std_lib_source_file(self) -> str:
132+
raise MesonException("Your compiler does not support 'import std' feature or it has not been implemented")
133+
134+
def get_cpp20_module_bmi_extension(self) -> str:
135+
raise MesonException("Your compiler does not support 'import std' feature or it has not been implemented")
136+
137+
def get_import_std_compile_args(self, environment: 'Environment'):
138+
raise MesonException("Your compiler does not support 'import std' feature or it has not been implemented")
139+
140+
def check_cpp_import_std_support(self):
141+
raise MesonException("Your compiler does not support 'import std' feature or it has not been implemented")
142+
143+
def _import_cpp_std_sanity_check(self, work_dir: str, environment: 'Environment') -> None:
144+
self.check_cpp_import_std_support()
92145

93146
def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
94147
# -fpermissive allows non-conforming code to compile which is necessary
@@ -174,9 +227,13 @@ def _find_best_cpp_std(self, cpp_std: str) -> str:
174227
def get_options(self) -> 'MutableKeyedOptionDictType':
175228
opts = super().get_options()
176229
key = self.form_compileropt_key('std')
230+
import_std_key = self.form_compileropt_key('import_std')
231+
177232
opts.update({
178233
key: options.UserStdOption('cpp', ALL_STDS),
234+
import_std_key: options.UseImportStd('cpp')
179235
})
236+
180237
return opts
181238

182239

@@ -235,6 +292,26 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_
235292
'3': default_warn_args + ['-Wextra', '-Wpedantic'],
236293
'everything': ['-Weverything']}
237294

295+
def check_cpp_import_std_support(self):
296+
if int(self.version.split('.')[0]) < 17:
297+
raise MesonException('Your compiler does not support import std feature. Clang support starts at version >= 17')
298+
299+
def get_import_std_compile_args(self, env: 'Environment'):
300+
bmi_path = f'{env.get_build_dir()}/std{self.get_cpp20_module_bmi_extension()}'
301+
return f'-fmodule-file=std={bmi_path}'
302+
303+
def get_cpp20_module_bmi_extension(self) -> str:
304+
return '.pcm'
305+
306+
#def get_compile_only_args(self) -> T.List[str]:
307+
# return [self.get_import_std_compile_args()].extend(CPPCompiler.get_compile_only_args(self))
308+
309+
def get_import_std_lib_source_file(self) -> str:
310+
comp = self.get_exelist(False)[0]
311+
bin_parent_dir = os.path.dirname(comp)
312+
llvm_dir = os.path.dirname(bin_parent_dir)
313+
return f'{llvm_dir}/share/libc++/v1/std.cppm'
314+
238315
def get_options(self) -> 'MutableKeyedOptionDictType':
239316
opts = super().get_options()
240317

mesonbuild/compilers/mixins/clike.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ def _sanity_check_impl(self, work_dir: str, environment: 'Environment',
287287
mode = CompileCheckMode.COMPILE
288288
cargs, largs = self._get_basic_compiler_args(environment, mode)
289289
extra_flags = cargs + self.linker_to_compiler_args(largs)
290+
if environment.coredata.optstore.get_value('cpp_import_std'):
291+
# extra_flags.extend(self.get_import_std)
292+
pass
290293

291294
# Is a valid executable output for all toolchains and platforms
292295
binname += '.exe'

mesonbuild/interpreter/interpreter.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,58 @@ def __init__(
309309
self.compilers: PerMachine[T.Dict[str, 'compilers.Compiler']] = PerMachine({}, {})
310310
self.parse_project()
311311
self._redetect_machines()
312+
self._cpp_import_std_bmi_dep = None
313+
314+
if self.coredata.optstore.get_value('cpp_import_std') and self.subproject == "":
315+
self._cpp_import_std_bmi_dep = self._create_cpp_import_std_dep(self.environment)
316+
317+
def _create_cpp_import_std_dep(self, env: environment.Environment):
318+
cpp_std = env.coredata.optstore.get_value('cpp_std')
319+
compiler_to_use: T.Optional[compilers.cpp.CPPCompiler] = None
320+
for comp_lang, compiler in self.compilers.host.items():
321+
if comp_lang == 'cpp':
322+
compiler_to_use = T.cast(compilers.cpp.CPPCompiler, compiler)
323+
if not compiler_to_use:
324+
raise MesonException('cpp_import_std option is set to true but no cpp compiler could be found.'
325+
' Enable cpp language in your project to use this feature.')
326+
cpp_std = env.coredata.optstore.get_value('cpp_std')
327+
# Construct the compiler command-line
328+
commands = compiler_to_use.compiler_args()
329+
commands.append(f"-std={cpp_std}")
330+
commands.extend(['-Wno-reserved-identifier', '-Wno-reserved-module-identifier'])
331+
commands.append("--precompile")
332+
333+
all_lists_to_add = [compiler_to_use.get_always_args(), compiler_to_use.get_debug_args(env.coredata.optstore.get_value('buildtype') == 'debug'),
334+
compiler_to_use.get_assert_args(disable=env.coredata.optstore.get_value('b_ndebug') in ['if-release', 'true'],
335+
env=env)]
336+
for args_list in all_lists_to_add:
337+
for arg in args_list:
338+
commands.append(arg)
339+
commands.append("-o")
340+
commands.append("@OUTPUT@")
341+
commands.append("@INPUT@")
342+
no_ccache = True
343+
command_list = compiler_to_use.get_exelist(ccache=not no_ccache) + commands.to_native()
344+
tgt = build.CustomTarget('',
345+
'', '', self.environment, command_list,
346+
sources=[compiler_to_use.get_import_std_lib_source_file()],
347+
outputs=[f'std{compiler_to_use.get_cpp20_module_bmi_extension()}'])
348+
self.add_target('_cpp_import_std_bmi', tgt)
349+
bmi_dep = dependencies.InternalDependency(
350+
version='0.0',
351+
incdirs=[],
352+
compile_args=[compiler_to_use.get_import_std_compile_args(self.environment)],
353+
link_args=[],
354+
libraries=[],
355+
whole_libraries=[],
356+
sources=[tgt],
357+
extra_files=[],
358+
ext_deps=[],
359+
variables=[],
360+
d_module_versions=[],
361+
d_import_dirs=[],
362+
objects=[])
363+
return bmi_dep
312364

313365
def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]:
314366
raise MesonBugException('This class is unpicklable')
@@ -3411,6 +3463,7 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs
34113463
if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}:
34123464
mlog.debug('Unknown target type:', str(targetclass))
34133465
raise RuntimeError('Unreachable code')
3466+
34143467
self.__process_language_args(kwargs)
34153468
if targetclass is build.StaticLibrary:
34163469
for lang in compilers.all_languages - {'java'}:
@@ -3492,6 +3545,10 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs
34923545

34933546
target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs,
34943547
self.environment, self.compilers[for_machine], kwargs)
3548+
if target.uses_cpp():
3549+
if self.coredata.optstore.get_value('cpp_import_std') and self.subproject == '':
3550+
target.add_deps([self._cpp_import_std_bmi_dep])
3551+
34953552
if objs and target.uses_rust():
34963553
FeatureNew.single_use('objects in Rust targets', '1.8.0', self.subproject)
34973554

mesonbuild/options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,12 @@ def choices_are_different(a: _U, b: _U) -> bool:
581581
return False
582582

583583

584+
class UseImportStd(UserBooleanOption):
585+
def __init__(self, lang):
586+
self.lang = lang.lower()
587+
opt_name =f'{self.lang}_import_std'
588+
super().__init__(opt_name, 'Whether to use import std; module in your targets', False)
589+
584590
class UserStdOption(UserComboOption):
585591
'''
586592
UserOption specific to c_std and cpp_std options. User can set a list of
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import std;
2+
3+
constexpr char const * const PROJECT_NAME = "import std";
4+
5+
int main(int argc, char **argv) {
6+
if (argc != 1) {
7+
std::cout << argv[0] << " takes no arguments.\n";
8+
return 1;
9+
}
10+
std::cout << "This is project " << PROJECT_NAME << ".\n";
11+
return 0;
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
project(
2+
'import std',
3+
'cpp',
4+
version : '0.1',
5+
meson_version : '>= 1.3.0',
6+
default_options : ['warning_level=3', 'cpp_std=c++23',
7+
'cpp_import_std=true'],
8+
)
9+
10+
11+
exe = executable(
12+
'import std',
13+
'import_std.cpp'
14+
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
project(
2+
'import std',
3+
'cpp',
4+
version : '0.1',
5+
meson_version : '>= 1.3.0',
6+
default_options : ['warning_level=3', 'cpp_std=c++23'],
7+
)
8+
9+
10+
cpp_comp = find_program('clang++', dirs: '/usr/local/Cellar/llvm/20.1.1/bin/')
11+
12+
cpp20_std_module = custom_target(
13+
'cpp20-std-module',
14+
command: [
15+
cpp_comp,
16+
'-std=c++23',
17+
'-Wno-reserved-identifier',
18+
'-Wno-reserved-module-identifier',
19+
'--precompile',
20+
'-o',
21+
'@OUTPUT@',
22+
'@INPUT@',
23+
],
24+
input: '/usr/local/Cellar/llvm/20.1.1/share/libc++/v1/std.cppm',
25+
output: 'std.pcm',
26+
)
27+
28+
cpp20_std_module_dep = declare_dependency(
29+
compile_args: ['-fmodule-file=std=@0@'.format(cpp20_std_module.full_path())],
30+
)
31+
32+
33+
dependencies = [cpp20_std_module_dep]
34+
35+
exe = executable(
36+
'import std',
37+
'import_std.cpp',
38+
install : true,
39+
dependencies : dependencies,
40+
)
41+
42+
test('basic', exe)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[binaries]
2+
c = '/usr/local/Cellar/llvm/20.1.1/bin/clang'
3+
cpp = '/usr/local/Cellar/llvm/20.1.1/bin/clang++'
4+
objc = '/usr/local/Cellar/llvm/20.1.1/bin/clang'
5+
objcpp = '/usr/local/Cellar/llvm/20.1.1/bin/clang++'
6+
7+
[built-in options]
8+
cpp_link_args='-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib/unwind -lunwind -L/usr/local/opt/llvm/lib'
9+
cpp_args = '-I/usr/local/opt/llvm/include'
10+
c_args = '-I/usr/local/opt/llvm/include'

0 commit comments

Comments
 (0)