aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Reference-manual.md4
-rw-r--r--docs/markdown/Users.md2
-rw-r--r--mesonbuild/backend/ninjabackend.py133
-rw-r--r--mesonbuild/compilers/compilers.py14
-rw-r--r--mesonbuild/compilers/mixins/clang.py8
-rw-r--r--mesonbuild/compilers/mixins/gnu.py18
-rw-r--r--mesonbuild/compilers/mixins/visualstudio.py11
-rw-r--r--mesonbuild/linkers.py33
-rw-r--r--mesonbuild/mesonlib.py4
-rw-r--r--mesonbuild/scripts/depscan.py91
-rwxr-xr-xrun_unittests.py10
-rw-r--r--test cases/unit/87 cpp modules/main.cpp7
-rw-r--r--test cases/unit/87 cpp modules/meson.build17
-rw-r--r--test cases/unit/87 cpp modules/src0.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src1.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src2.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src3.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src4.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src5.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src6.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src7.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src8.ixx7
-rw-r--r--test cases/unit/87 cpp modules/src9.ixx5
23 files changed, 362 insertions, 58 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index f490f5f..924047c 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -1280,7 +1280,7 @@ them for the default behaviour for each platform.
This function prints its argument to stdout.
-*(since 0.54.0)* Can take more more than one argument that will be separated by
+*(since 0.54.0)* Can take more than one argument that will be separated by
space.
### warning()
@@ -1293,7 +1293,7 @@ space.
This function prints its argument to stdout prefixed with WARNING:.
-*(since 0.54.0)* Can take more more than one argument that will be separated by
+*(since 0.54.0)* Can take more than one argument that will be separated by
space.
### summary()
diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md
index 366a0c3..405757d 100644
--- a/docs/markdown/Users.md
+++ b/docs/markdown/Users.md
@@ -85,7 +85,7 @@ topic](https://github.com/topics/meson).
- [Libosmscout](https://github.com/Framstag/libosmscout), a C++ library for offline map rendering, routing and location
lookup based on OpenStreetMap data
- [libratbag](https://github.com/libratbag/libratbag), provides a DBus daemon to configure input devices, mainly gaming mice.
- - [libspng](https://gitlab.com/randy408/libspng), a C library for reading and writing Portable Network Graphics (PNG)
+ - [libspng](https://github.com/randy408/libspng), a C library for reading and writing Portable Network Graphics (PNG)
format files
- [libui](https://github.com/andlabs/libui), a simple and portable (but not inflexible) GUI library in C that uses the native GUI technologies of each platform it supports
- [Libva](https://github.com/intel/libva), an implementation for the VA (VIdeo Acceleration) API
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 0fe8a2f..e643754 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -112,7 +112,7 @@ rsp_threshold = get_rsp_threshold()
# variables (or variables we use them in) is interpreted directly by ninja
# (e.g. the value of the depfile variable is a pathname that ninja will read
# from, etc.), so it must not be shell quoted.
-raw_names = {'DEPFILE_UNQUOTED', 'DESC', 'pool', 'description', 'targetdep'}
+raw_names = {'DEPFILE_UNQUOTED', 'DESC', 'pool', 'description', 'targetdep', 'dyndep'}
NINJA_QUOTE_BUILD_PAT = re.compile(r"[$ :\n]")
NINJA_QUOTE_VAR_PAT = re.compile(r"[$ \n]")
@@ -134,6 +134,11 @@ Please report this error with a test case to the Meson bug tracker.'''.format(te
raise MesonException(errmsg)
return quote_re.sub(r'$\g<0>', text)
+class TargetDependencyScannerInfo:
+ def __init__(self, private_dir: str, source2object: T.Dict[str, str]):
+ self.private_dir = private_dir
+ self.source2object = source2object
+
@unique
class Quoting(Enum):
both = 0
@@ -683,10 +688,17 @@ int dummy;
return False
def generate_target(self, target):
+ try:
+ if isinstance(target, build.BuildTarget):
+ os.makedirs(self.get_target_private_dir_abs(target))
+ except FileExistsError:
+ pass
if isinstance(target, build.CustomTarget):
self.generate_custom_target(target)
if isinstance(target, build.RunTarget):
self.generate_run_target(target)
+ compiled_sources = []
+ source2object = {}
name = target.get_id()
if name in self.processed_targets:
return
@@ -784,10 +796,12 @@ int dummy;
# because we need `header_deps` to be fully generated in the above loop.
for src in generated_source_files:
if self.environment.is_llvm_ir(src):
- o = self.generate_llvm_ir_compile(target, src)
+ o, s = self.generate_llvm_ir_compile(target, src)
else:
- o = self.generate_single_compile(target, src, True,
+ o, s = self.generate_single_compile(target, src, True,
order_deps=header_deps)
+ compiled_sources.append(s)
+ source2object[s] = o
obj_list.append(o)
use_pch = self.environment.coredata.base_options.get('b_pch', False)
@@ -822,32 +836,84 @@ int dummy;
# Passing 'vala' here signifies that we want the compile
# arguments to be specialized for C code generated by
# valac. For instance, no warnings should be emitted.
- obj_list.append(self.generate_single_compile(target, src, 'vala', [], header_deps))
+ o, s = self.generate_single_compile(target, src, 'vala', [], header_deps)
+ obj_list.append(o)
# Generate compile targets for all the pre-existing sources for this target
for src in target_sources.values():
if not self.environment.is_header(src):
if self.environment.is_llvm_ir(src):
- obj_list.append(self.generate_llvm_ir_compile(target, src))
+ o, s = self.generate_llvm_ir_compile(target, src)
+ obj_list.append(o)
elif is_unity and self.get_target_source_can_unity(target, src):
abs_src = os.path.join(self.environment.get_build_dir(),
src.rel_to_builddir(self.build_to_src))
unity_src.append(abs_src)
else:
- obj_list.append(self.generate_single_compile(target, src, False, [], header_deps))
+ o, s = self.generate_single_compile(target, src, False, [], header_deps)
+ obj_list.append(o)
+ compiled_sources.append(s)
+ source2object[s] = o
+
obj_list += self.flatten_object_list(target)
if is_unity:
for src in self.generate_unity_files(target, unity_src):
- obj_list.append(self.generate_single_compile(target, src, True, unity_deps + header_deps))
+ o, s = self.generate_single_compile(target, src, True, unity_deps + header_deps)
+ obj_list.append(o)
+ compiled_sources.append(s)
+ source2object[s] = o
linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
if isinstance(target, build.StaticLibrary) and target.prelink:
final_obj_list = self.generate_prelink(target, obj_list)
else:
final_obj_list = obj_list
elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args)
+ self.generate_dependency_scan_target(target, compiled_sources, source2object)
self.generate_shlib_aliases(target, self.get_target_dir(target))
self.add_build(elem)
+ def should_scan_target(self, target):
+ if 'cpp' not in target.compilers:
+ return False
+ # Currently only the preview version of Visual Studio is supported.
+ cpp = target.compilers['cpp']
+ if cpp.get_id() != 'msvc':
+ return False
+ if not mesonlib.current_vs_supports_modules():
+ return False
+ if mesonlib.version_compare(cpp.version, '<19.28.28617'):
+ return False
+ if mesonlib.version_compare(self.ninja_version, '<1.10.0'):
+ return False
+ return True
+
+ def generate_dependency_scan_target(self, target, compiled_sources, source2object):
+ if not self.should_scan_target(target):
+ return
+ depscan_file = self.get_dep_scan_file_for(target)
+ pickle_base = target.name + '.dat'
+ pickle_file = os.path.join(self.get_target_private_dir(target), pickle_base).replace('\\', '/')
+ pickle_abs = os.path.join(self.get_target_private_dir_abs(target), pickle_base).replace('\\', '/')
+ rule_name = 'cppscan'
+ scan_sources = self.select_sources_to_scan(compiled_sources)
+ elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, scan_sources)
+ elem.add_item('picklefile', pickle_file)
+ scaninfo = TargetDependencyScannerInfo(self.get_target_private_dir(target), source2object)
+ with open(pickle_abs, 'wb') as p:
+ pickle.dump(scaninfo, p)
+ self.add_build(elem)
+
+ def select_sources_to_scan(self, compiled_sources):
+ # in practice pick up C++ and Fortran files. If some other language
+ # requires scanning (possibly Java to deal with inner class files)
+ # then add them here.
+ selected_sources = []
+ for source in compiled_sources:
+ ext = os.path.splitext(source)[1][1:]
+ if ext in compilers.lang_suffixes['cpp']:
+ selected_sources.append(source)
+ return selected_sources
+
def process_target_dependencies(self, target):
for t in target.get_dependencies():
if t.get_id() not in self.processed_targets:
@@ -1074,6 +1140,8 @@ int dummy;
self.rules = []
self.ruledict = {}
+ self.add_rule_comment(NinjaComment('Rules for module scanning.'))
+ self.generate_scanner_rules()
self.add_rule_comment(NinjaComment('Rules for compiling.'))
self.generate_compile_rules()
self.add_rule_comment(NinjaComment('Rules for linking.'))
@@ -1107,6 +1175,8 @@ int dummy;
self.build_elements.append(comment)
def add_rule(self, rule):
+ if rule.name in self.ruledict:
+ raise MesonException('Tried to add rule {} twice.'.format(rule.name))
self.rules.append(rule)
self.ruledict[rule.name] = rule
@@ -1957,6 +2027,26 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.add_rule(NinjaRule(rule, command, [], description, deps=deps,
depfile=depfile))
+
+ def generate_scanner_rules(self):
+ scanner_languages = {'cpp'} # Fixme, add Fortran.
+ for for_machine in MachineChoice:
+ clist = self.environment.coredata.compilers[for_machine]
+ for langname, compiler in clist.items():
+ if langname not in scanner_languages:
+ continue
+ rulename = '{}scan'.format(langname)
+ if rulename in self.ruledict:
+ # Scanning command is the same for native and cross compilation.
+ continue
+ command = self.environment.get_build_command() + \
+ ['--internal', 'depscan']
+ args = ['$picklefile', '$out', '$in']
+ description = 'Module scanner for {}.'.format(langname)
+ rule = NinjaRule(rulename, command, args, description)
+ self.add_rule(rule)
+
+
def generate_compile_rules(self):
for for_machine in MachineChoice:
clist = self.environment.coredata.compilers[for_machine]
@@ -2217,7 +2307,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
element.add_item('ARGS', commands)
self.add_build(element)
- return rel_obj
+ return (rel_obj, rel_src)
def get_source_dir_include_args(self, target, compiler):
curdir = target.get_subdir()
@@ -2439,8 +2529,22 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
element.add_orderdep(i)
element.add_item('DEPFILE', dep_file)
element.add_item('ARGS', commands)
+
+ self.add_dependency_scanner_entries_to_element(target, compiler, element)
self.add_build(element)
- return rel_obj
+ assert(isinstance(rel_obj, str))
+ assert(isinstance(rel_src, str))
+ return (rel_obj, rel_src.replace('\\', '/'))
+
+ def add_dependency_scanner_entries_to_element(self, target, compiler, element):
+ if not self.should_scan_target(target):
+ return
+ dep_scan_file = self.get_dep_scan_file_for(target)
+ element.add_item('dyndep', dep_scan_file)
+ element.add_orderdep(dep_scan_file)
+
+ def get_dep_scan_file_for(self, target):
+ return os.path.join(self.get_target_private_dir(target), 'depscan.dd')
def add_header_deps(self, target, ninja_element, header_deps):
for d in header_deps:
@@ -2598,10 +2702,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
# If gui_app is significant on this platform, add the appropriate linker arguments.
# Unfortunately this can't be done in get_target_type_link_args, because some misguided
# libraries (such as SDL2) add -mwindows to their link flags.
- if target.gui_app is not None:
- commands += linker.get_gui_app_args(target.gui_app)
- else:
- commands += linker.get_win_subsystem_args(target.win_subsystem)
+ m = self.environment.machines[target.for_machine]
+
+ if m.is_windows() or m.is_cygwin():
+ if target.gui_app is not None:
+ commands += linker.get_gui_app_args(target.gui_app)
+ else:
+ commands += linker.get_win_subsystem_args(target.win_subsystem)
return commands
def get_link_whole_args(self, linker, target):
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 2900e19..0bd2b4c 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -55,7 +55,7 @@ lib_suffixes = ('a', 'lib', 'dll', 'dll.a', 'dylib', 'so') # type: T.Tuple[str,
# This means we can't include .h headers here since they could be C, C++, ObjC, etc.
lang_suffixes = {
'c': ('c',),
- 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino'),
+ 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino', 'ixx'),
'cuda': ('cu',),
# f90, f95, f03, f08 are for free-form fortran ('f90' recommended)
# f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended)
@@ -871,14 +871,14 @@ class Compiler(metaclass=abc.ABCMeta):
return []
def get_gui_app_args(self, value: bool) -> T.List[str]:
- return []
+ # Only used on Windows
+ return self.linker.get_gui_app_args(value)
def get_win_subsystem_args(self, value: str) -> T.List[str]:
- # This returns an empty array rather than throws to simplify the code.
- # Otherwise we would have to check whenever calling this function whether
- # the target is for Windows. There are also many cases where this is
- # a meaningless choice, such as with Jave or C#.
- return []
+ # By default the dynamic linker is going to return an empty
+ # array in case it either doesn't support Windows subsystems
+ # or does not target Windows
+ return self.linker.get_win_subsystem_args(value)
def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]:
raise EnvironmentException(
diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py
index ab2d066..2e50577 100644
--- a/mesonbuild/compilers/mixins/clang.py
+++ b/mesonbuild/compilers/mixins/clang.py
@@ -19,7 +19,7 @@ import shutil
import typing as T
from ... import mesonlib
-from ...linkers import AppleDynamicLinker, ClangClDynamicLinker
+from ...linkers import AppleDynamicLinker
from ..compilers import CompileCheckMode
from .gnu import GnuLikeCompiler
@@ -109,12 +109,6 @@ class ClangCompiler(GnuLikeCompiler):
# Shouldn't work, but it'll be checked explicitly in the OpenMP dependency.
return []
- def get_win_subsystem_args(self, value: str) -> T.List[str]:
- if self.info.is_windows() and not self.info.is_cygwin() and isinstance(self.linker, ClangClDynamicLinker):
- return [f'-Wl,/subsystem:{value}']
-
- return super().get_win_subsystem_args(value)
-
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
# Clang additionally can use a linker specified as a path, which GCC
diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py
index 4024cbe..3d43162 100644
--- a/mesonbuild/compilers/mixins/gnu.py
+++ b/mesonbuild/compilers/mixins/gnu.py
@@ -215,23 +215,7 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta):
return ['-fprofile-use', '-fprofile-correction']
def get_gui_app_args(self, value: bool) -> T.List[str]:
- if self.info.is_windows() or self.info.is_cygwin():
- return ['-mwindows' if value else '-mconsole']
- return []
-
- def get_win_subsystem_args(self, value: str) -> T.List[str]:
- args = []
- if self.info.is_windows() or self.info.is_cygwin():
- if 'windows' in value:
- args = ['-Wl,--subsystem,windows']
- elif 'console' in value:
- args = ['-Wl,--subsystem,console']
- else:
- raise mesonlib.MesonException('Only "windows" and "console" are supported for win_subsystem with MinGW, not "{}".'.format(value))
- if ',' in value:
- args[-1] = args[-1] + ':' + value.split(',')[1]
- return args
-
+ return ['-mwindows' if value else '-mconsole']
def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]:
for idx, i in enumerate(parameter_list):
diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py
index 3e8b8e3..c38d59a 100644
--- a/mesonbuild/compilers/mixins/visualstudio.py
+++ b/mesonbuild/compilers/mixins/visualstudio.py
@@ -197,17 +197,6 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta):
def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]:
return ['/link'] + args
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- # the default is for the linker to guess the subsystem based on presence
- # of main or WinMain symbols, so always be explicit
- if value:
- return ['/SUBSYSTEM:WINDOWS']
- else:
- return ['/SUBSYSTEM:CONSOLE']
-
- def get_win_subsystem_args(self, value: str) -> T.List[str]:
- return ['/SUBSYSTEM:' + value.upper()]
-
def get_pic_args(self) -> T.List[str]:
return [] # PIC is handled by the loader on Windows
diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py
index 967e465..e74457b 100644
--- a/mesonbuild/linkers.py
+++ b/mesonbuild/linkers.py
@@ -477,6 +477,15 @@ class DynamicLinker(LinkerEnvVarsMixin, metaclass=abc.ABCMeta):
# Only used by the Apple linker
return []
+ def get_gui_app_args(self, value: bool) -> T.List[str]:
+ # Only used by VisualStudioLikeLinkers
+ return []
+
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ # Only used if supported by the dynamic linker and
+ # only when targeting Windows
+ return []
+
def bitcode_args(self) -> T.List[str]:
raise mesonlib.MesonException('This linker does not support bitcode bundles')
@@ -659,6 +668,18 @@ class GnuLikeDynamicLinkerMixin:
return (args, rpath_dirs_to_remove)
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ if 'windows' in value:
+ args = ['--subsystem,windows']
+ elif 'console' in value:
+ args = ['--subsystem,console']
+ else:
+ raise mesonlib.MesonException(f'Only "windows" and "console" are supported for win_subsystem with MinGW, not "{value}".')
+ if ',' in value:
+ args[-1] = args[-1] + ':' + value.split(',')[1]
+
+ return self._apply_prefix(args)
+
class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):
@@ -1149,6 +1170,12 @@ class MSVCDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
def get_always_args(self) -> T.List[str]:
return self._apply_prefix(['/nologo', '/release']) + super().get_always_args()
+ def get_gui_app_args(self, value: bool) -> T.List[str]:
+ return self.get_win_subsystem_args("windows" if value else "console")
+
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}'])
+
class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
@@ -1172,6 +1199,12 @@ class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
return super().get_output_args(outputname)
+ def get_gui_app_args(self, value: bool) -> T.List[str]:
+ return self.get_win_subsystem_args("windows" if value else "console")
+
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}'])
+
class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index d46dfca..e773144 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -586,6 +586,10 @@ def detect_vcs(source_dir: T.Union[str, Path]) -> T.Optional[T.Dict[str, str]]:
return vcs
return None
+def current_vs_supports_modules() -> bool:
+ vsver = os.environ.get('VSCMD_VER', '')
+ return vsver.startswith('16.9.0') and '-pre.' in vsver
+
# a helper class which implements the same version ordering as RPM
class Version:
def __init__(self, s: str) -> None:
diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py
new file mode 100644
index 0000000..6eebbfe
--- /dev/null
+++ b/mesonbuild/scripts/depscan.py
@@ -0,0 +1,91 @@
+# Copyright 2020 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pathlib
+import pickle
+import re
+import typing as T
+
+from ..backend.ninjabackend import TargetDependencyScannerInfo
+
+import_re = re.compile('\w*import ([a-zA-Z0-9]+);')
+export_re = re.compile('\w*export module ([a-zA-Z0-9]+);')
+
+class DependencyScanner:
+ def __init__(self, pickle_file: str, outfile: str, sources: T.List[str]):
+ with open(pickle_file, 'rb') as pf:
+ self.target_data = pickle.load(pf) # type: TargetDependencyScannerInfo
+ self.outfile = outfile
+ self.sources = sources
+ self.provided_by = {} # type: T.Dict[str, str]
+ self.exports = {} # type: T.Dict[str, str]
+ self.needs = {} # type: T.Dict[str, T.List[str]]
+ self.sources_with_exports = [] # type: T.List[str]
+
+ def scan_file(self, fname: str) -> None:
+ for line in pathlib.Path(fname).read_text().split('\n'):
+ import_match = import_re.match(line)
+ export_match = export_re.match(line)
+ if import_match:
+ needed = import_match.group(1)
+ if fname in self.needs:
+ self.needs[fname].append(needed)
+ else:
+ self.needs[fname] = [needed]
+ if export_match:
+ exported_module = export_match.group(1)
+ if exported_module in self.provided_by:
+ raise RuntimeError('Multiple files provide module {}.'.format(exported_module))
+ self.sources_with_exports.append(fname)
+ self.provided_by[exported_module] = fname
+ self.exports[fname] = exported_module
+
+ def objname_for(self, src: str) -> str:
+ objname = self.target_data.source2object[src]
+ assert(isinstance(objname, str))
+ return objname
+
+ def ifcname_for(self, src: str) -> str:
+ return '{}.ifc'.format(self.exports[src])
+
+ def scan(self) -> int:
+ for s in self.sources:
+ self.scan_file(s)
+ with open(self.outfile, 'w') as ofile:
+ ofile.write('ninja_dyndep_version = 1\n')
+ for src in self.sources:
+ objfilename = self.objname_for(src)
+ if src in self.sources_with_exports:
+ ifc_entry = '| ' + self.ifcname_for(src)
+ else:
+ ifc_entry = ''
+ if src in self.needs:
+ # FIXME, handle all sources, not just the first one
+ modname = self.needs[src][0]
+ provider_src = self.provided_by[modname]
+ provider_ifc = self.ifcname_for(provider_src)
+ mod_dep = '| ' + provider_ifc
+ else:
+ mod_dep = ''
+ ofile.write('build {} {}: dyndep {}\n'.format(objfilename,
+ ifc_entry,
+ mod_dep))
+ return 0
+
+def run(args: T.List[str]) -> int:
+ pickle_file = args[0]
+ outfile = args[1]
+ sources = args[2:]
+ scanner = DependencyScanner(pickle_file, outfile, sources)
+ return scanner.scan()
diff --git a/run_unittests.py b/run_unittests.py
index b9db708..4b14a87 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -5821,6 +5821,16 @@ class WindowsTests(BasePlatformTests):
self.init(testdir, extra_args=['-Db_vscrt=mtd'])
sanitycheck_vscrt('/MTd')
+ def test_modules(self):
+ if self.backend is not Backend.ninja:
+ raise unittest.SkipTest('C++ modules only work with the Ninja backend (not {}).'.format(self.backend.name))
+ if 'VSCMD_VER' not in os.environ:
+ raise unittest.SkipTest('C++ modules is only supported with Visual Studio.')
+ if version_compare(os.environ['VSCMD_VER'], '<16.9.0'):
+ raise unittest.SkipTest('C++ modules are only supported with VS 2019 Preview or newer.')
+ self.init(os.path.join(self.unit_test_dir, '87 cpp modules'))
+ self.build()
+
@unittest.skipUnless(is_osx(), "requires Darwin")
class DarwinTests(BasePlatformTests):
diff --git a/test cases/unit/87 cpp modules/main.cpp b/test cases/unit/87 cpp modules/main.cpp
new file mode 100644
index 0000000..d825c7d
--- /dev/null
+++ b/test cases/unit/87 cpp modules/main.cpp
@@ -0,0 +1,7 @@
+import M0;
+#include<cstdio>
+
+int main() {
+ printf("The value is %d", func0());
+ return 0;
+}
diff --git a/test cases/unit/87 cpp modules/meson.build b/test cases/unit/87 cpp modules/meson.build
new file mode 100644
index 0000000..cdc296f
--- /dev/null
+++ b/test cases/unit/87 cpp modules/meson.build
@@ -0,0 +1,17 @@
+project('cppmodules', 'cpp', default_options: ['cpp_std=c++latest'])
+
+e = executable('modtest',
+ 'main.cpp',
+ 'src0.ixx',
+ 'src1.ixx',
+ 'src2.ixx',
+ 'src3.ixx',
+ 'src4.ixx',
+ 'src5.ixx',
+ 'src6.ixx',
+ 'src7.ixx',
+ 'src8.ixx',
+ 'src9.ixx',
+ )
+
+test('modtest', e)
diff --git a/test cases/unit/87 cpp modules/src0.ixx b/test cases/unit/87 cpp modules/src0.ixx
new file mode 100644
index 0000000..3ca4d14
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src0.ixx
@@ -0,0 +1,7 @@
+export module M0;
+
+import M1;
+
+export int func0() {
+ return func1();
+}
diff --git a/test cases/unit/87 cpp modules/src1.ixx b/test cases/unit/87 cpp modules/src1.ixx
new file mode 100644
index 0000000..cea6696
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src1.ixx
@@ -0,0 +1,7 @@
+export module M1;
+
+import M2;
+
+export int func1() {
+ return func2();
+}
diff --git a/test cases/unit/87 cpp modules/src2.ixx b/test cases/unit/87 cpp modules/src2.ixx
new file mode 100644
index 0000000..415714c
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src2.ixx
@@ -0,0 +1,7 @@
+export module M2;
+
+import M3;
+
+export int func2() {
+ return func3();
+}
diff --git a/test cases/unit/87 cpp modules/src3.ixx b/test cases/unit/87 cpp modules/src3.ixx
new file mode 100644
index 0000000..96f135c
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src3.ixx
@@ -0,0 +1,7 @@
+export module M3;
+
+import M4;
+
+export int func3() {
+ return func4();
+}
diff --git a/test cases/unit/87 cpp modules/src4.ixx b/test cases/unit/87 cpp modules/src4.ixx
new file mode 100644
index 0000000..1ac1a6a
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src4.ixx
@@ -0,0 +1,7 @@
+export module M4;
+
+import M5;
+
+export int func4() {
+ return func5();
+}
diff --git a/test cases/unit/87 cpp modules/src5.ixx b/test cases/unit/87 cpp modules/src5.ixx
new file mode 100644
index 0000000..96cf707
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src5.ixx
@@ -0,0 +1,7 @@
+export module M5;
+
+import M6;
+
+export int func5() {
+ return func6();
+}
diff --git a/test cases/unit/87 cpp modules/src6.ixx b/test cases/unit/87 cpp modules/src6.ixx
new file mode 100644
index 0000000..760b71c
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src6.ixx
@@ -0,0 +1,7 @@
+export module M6;
+
+import M7;
+
+export int func6() {
+ return func7();
+}
diff --git a/test cases/unit/87 cpp modules/src7.ixx b/test cases/unit/87 cpp modules/src7.ixx
new file mode 100644
index 0000000..8ce6608
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src7.ixx
@@ -0,0 +1,7 @@
+export module M7;
+
+import M8;
+
+export int func7() {
+ return func8();
+}
diff --git a/test cases/unit/87 cpp modules/src8.ixx b/test cases/unit/87 cpp modules/src8.ixx
new file mode 100644
index 0000000..6a3ef96
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src8.ixx
@@ -0,0 +1,7 @@
+export module M8;
+
+import M9;
+
+export int func8() {
+ return func9();
+}
diff --git a/test cases/unit/87 cpp modules/src9.ixx b/test cases/unit/87 cpp modules/src9.ixx
new file mode 100644
index 0000000..3ecb3be
--- /dev/null
+++ b/test cases/unit/87 cpp modules/src9.ixx
@@ -0,0 +1,5 @@
+export module M9;
+
+export int func9() {
+ return 42;
+}