From b8cb53791e7211c5dd8ba7c397e35e4280300e11 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 21 Nov 2020 19:37:05 +0200 Subject: [PATCH 1/7] Generate scanning rules for C++ modules. --- mesonbuild/backend/ninjabackend.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 0fe8a2f85..de187c422 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1074,6 +1074,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 +1109,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 +1961,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 = cmd = self.environment.get_build_command() + \ + ['--internal', 'scan'] + 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] From 5bd1276d2024bf84ea5dfe1b1b43f39cc13b0764 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 21 Nov 2020 20:10:58 +0200 Subject: [PATCH 2/7] Create unit test for C++ modules and accept ixx as C++ source extension. --- mesonbuild/compilers/compilers.py | 2 +- test cases/unit/87 cpp modules/main.cpp | 7 +++++++ test cases/unit/87 cpp modules/meson.build | 17 +++++++++++++++++ test cases/unit/87 cpp modules/src0.ixx | 7 +++++++ test cases/unit/87 cpp modules/src1.ixx | 7 +++++++ test cases/unit/87 cpp modules/src2.ixx | 7 +++++++ test cases/unit/87 cpp modules/src3.ixx | 7 +++++++ test cases/unit/87 cpp modules/src4.ixx | 7 +++++++ test cases/unit/87 cpp modules/src5.ixx | 7 +++++++ test cases/unit/87 cpp modules/src6.ixx | 7 +++++++ test cases/unit/87 cpp modules/src7.ixx | 7 +++++++ test cases/unit/87 cpp modules/src8.ixx | 7 +++++++ test cases/unit/87 cpp modules/src9.ixx | 5 +++++ 13 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 test cases/unit/87 cpp modules/main.cpp create mode 100644 test cases/unit/87 cpp modules/meson.build create mode 100644 test cases/unit/87 cpp modules/src0.ixx create mode 100644 test cases/unit/87 cpp modules/src1.ixx create mode 100644 test cases/unit/87 cpp modules/src2.ixx create mode 100644 test cases/unit/87 cpp modules/src3.ixx create mode 100644 test cases/unit/87 cpp modules/src4.ixx create mode 100644 test cases/unit/87 cpp modules/src5.ixx create mode 100644 test cases/unit/87 cpp modules/src6.ixx create mode 100644 test cases/unit/87 cpp modules/src7.ixx create mode 100644 test cases/unit/87 cpp modules/src8.ixx create mode 100644 test cases/unit/87 cpp modules/src9.ixx diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 2900e19a4..2acd429c8 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) 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 000000000..d825c7d49 --- /dev/null +++ b/test cases/unit/87 cpp modules/main.cpp @@ -0,0 +1,7 @@ +import M0; +#include + +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 000000000..cdc296f43 --- /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 000000000..3ca4d1448 --- /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 000000000..cea6696de --- /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 000000000..415714cc0 --- /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 000000000..96f135c00 --- /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 000000000..1ac1a6a64 --- /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 000000000..96cf70761 --- /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 000000000..760b71cbb --- /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 000000000..8ce660801 --- /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 000000000..6a3ef96f5 --- /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 000000000..3ecb3be68 --- /dev/null +++ b/test cases/unit/87 cpp modules/src9.ixx @@ -0,0 +1,5 @@ +export module M9; + +export int func9() { + return 42; +} From 92e94b1e92350073e8a3a1bd69daa3a1bbe93e61 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 21 Nov 2020 21:30:58 +0200 Subject: [PATCH 3/7] Generate dependency scanning hooks in the Ninja file. --- mesonbuild/backend/ninjabackend.py | 54 +++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index de187c422..dffff56e4 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]") @@ -687,6 +687,8 @@ int dummy; 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 +786,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 +826,52 @@ 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[src] = 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[src] = 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 generate_dependency_scan_target(self, target, compiled_sources, source2object): + if 'cpp' not in target.compilers: + return + depscan_file = self.get_dep_scan_file_for(target) + pickle_file = os.path.join(self.get_target_private_dir(target), target.name + '.dat').replace('\\', '/') + rule_name = 'cppscan' + elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, compiled_sources) + elem.add_item('picklefile', pickle_file) + self.add_build(elem) + def process_target_dependencies(self, target): for t in target.get_dependencies(): if t.get_id() not in self.processed_targets: @@ -2241,7 +2265,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() @@ -2463,8 +2487,20 @@ 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 + return (rel_obj, rel_src) + + def add_dependency_scanner_entries_to_element(self, target, compiler, element): + if compiler.get_language() != 'cpp': + 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: From f390d227879512c87a7ddd83fe3b649ba45b5442 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 21 Nov 2020 22:34:08 +0200 Subject: [PATCH 4/7] C++ module compilation works for a simple project. --- mesonbuild/backend/ninjabackend.py | 26 +++++++-- mesonbuild/scripts/depscan.py | 87 ++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 mesonbuild/scripts/depscan.py diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index dffff56e4..ea04079bd 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -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, source2object): + self.private_dir = private_dir + self.source2object = source2object + @unique class Quoting(Enum): both = 0 @@ -683,6 +688,10 @@ int dummy; return False def generate_target(self, target): + try: + 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): @@ -843,7 +852,7 @@ int dummy; o, s = self.generate_single_compile(target, src, False, [], header_deps) obj_list.append(o) compiled_sources.append(s) - source2object[src] = o + source2object[s] = o obj_list += self.flatten_object_list(target) if is_unity: @@ -851,7 +860,7 @@ int dummy; o, s = self.generate_single_compile(target, src, True, unity_deps + header_deps) obj_list.append(o) compiled_sources.append(s) - source2object[src] = o + 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) @@ -866,10 +875,15 @@ int dummy; if 'cpp' not in target.compilers: return depscan_file = self.get_dep_scan_file_for(target) - pickle_file = os.path.join(self.get_target_private_dir(target), target.name + '.dat').replace('\\', '/') + 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' elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, compiled_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 process_target_dependencies(self, target): @@ -1998,7 +2012,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # Scanning command is the same for native and cross compilation. continue command = cmd = self.environment.get_build_command() + \ - ['--internal', 'scan'] + ['--internal', 'depscan'] args = ['$picklefile', '$out', '$in'] description = 'Module scanner for {}.'.format(langname) rule = NinjaRule(rulename, command, args, description) @@ -2490,7 +2504,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.add_dependency_scanner_entries_to_element(target, compiler, element) self.add_build(element) - return (rel_obj, rel_src) + 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 compiler.get_language() != 'cpp': diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py new file mode 100644 index 000000000..161faf81e --- /dev/null +++ b/mesonbuild/scripts/depscan.py @@ -0,0 +1,87 @@ +# 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 + +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, outfile, sources): + with open(pickle_file, 'rb') as pf: + self.target_data = pickle.load(pf) + self.outfile = outfile + self.sources = sources + self.provided_by = {} + self.exports = {} + self.needs = {} + self.sources_with_exports = [] + + def scan_file(self, fname): + 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): + return self.target_data.source2object[src] + + def ifcname_for(self, src): + return '{}.ifc'.format(self.exports[src]) + + def scan(self): + 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() From 3df064484a09ef9a1ece98ea74e9c87855994d63 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 21 Nov 2020 23:01:27 +0200 Subject: [PATCH 5/7] Add unit test. --- run_unittests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/run_unittests.py b/run_unittests.py index 84719d676..819c04579 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): From 26ffd4f8f2bca3f858cca829f4c356c6ce97c371 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Fri, 11 Dec 2020 23:35:24 +0200 Subject: [PATCH 6/7] Scan all C++ sources and ignore everything else. --- mesonbuild/backend/ninjabackend.py | 38 ++++++++++++++++++++++++++---- mesonbuild/mesonlib.py | 4 ++++ mesonbuild/scripts/depscan.py | 2 ++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index ea04079bd..159d6de02 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -689,7 +689,8 @@ int dummy; def generate_target(self, target): try: - os.makedirs(self.get_target_private_dir_abs(target)) + if isinstance(target, build.BuildTarget): + os.makedirs(self.get_target_private_dir_abs(target)) except FileExistsError: pass if isinstance(target, build.CustomTarget): @@ -871,21 +872,48 @@ int dummy; self.generate_shlib_aliases(target, self.get_target_dir(target)) self.add_build(elem) - def generate_dependency_scan_target(self, target, compiled_sources, source2object): + 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' - elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, compiled_sources) + 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: @@ -2011,7 +2039,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if rulename in self.ruledict: # Scanning command is the same for native and cross compilation. continue - command = cmd = self.environment.get_build_command() + \ + command = self.environment.get_build_command() + \ ['--internal', 'depscan'] args = ['$picklefile', '$out', '$in'] description = 'Module scanner for {}.'.format(langname) @@ -2509,7 +2537,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return (rel_obj, rel_src.replace('\\', '/')) def add_dependency_scanner_entries_to_element(self, target, compiler, element): - if compiler.get_language() != 'cpp': + 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) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index d46dfcafa..e77314496 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 index 161faf81e..907ce5a27 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -17,6 +17,8 @@ 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]+);') From f22d54690ba81da5ba5db7a981e3cfdb04de6ef2 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 12 Dec 2020 18:48:27 +0200 Subject: [PATCH 7/7] Add mypy annotations. --- mesonbuild/backend/ninjabackend.py | 2 +- mesonbuild/scripts/depscan.py | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 159d6de02..71deab01b 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -135,7 +135,7 @@ Please report this error with a test case to the Meson bug tracker.'''.format(te return quote_re.sub(r'$\g<0>', text) class TargetDependencyScannerInfo: - def __init__(self, private_dir, source2object): + def __init__(self, private_dir: str, source2object: T.Dict[str, str]): self.private_dir = private_dir self.source2object = source2object diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index 907ce5a27..6eebbfe6a 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -23,17 +23,17 @@ 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, outfile, sources): + 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) + self.target_data = pickle.load(pf) # type: TargetDependencyScannerInfo self.outfile = outfile self.sources = sources - self.provided_by = {} - self.exports = {} - self.needs = {} - self.sources_with_exports = [] + 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): + 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) @@ -51,13 +51,15 @@ class DependencyScanner: self.provided_by[exported_module] = fname self.exports[fname] = exported_module - def objname_for(self, src): - return self.target_data.source2object[src] + def objname_for(self, src: str) -> str: + objname = self.target_data.source2object[src] + assert(isinstance(objname, str)) + return objname - def ifcname_for(self, src): + def ifcname_for(self, src: str) -> str: return '{}.ifc'.format(self.exports[src]) - def scan(self): + def scan(self) -> int: for s in self.sources: self.scan_file(s) with open(self.outfile, 'w') as ofile: