diff options
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 85 | ||||
-rw-r--r-- | mesonbuild/scripts/depscan.py | 65 |
2 files changed, 106 insertions, 44 deletions
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 71deab0..6c60958 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -872,7 +872,11 @@ int dummy; self.generate_shlib_aliases(target, self.get_target_dir(target)) self.add_build(elem) - def should_scan_target(self, target): + def should_use_dyndeps_for_target(self, target): + if mesonlib.version_compare(self.ninja_version, '<1.10.0'): + return False + if 'fortran' in target.compilers: + return True if 'cpp' not in target.compilers: return False # Currently only the preview version of Visual Studio is supported. @@ -883,18 +887,16 @@ int dummy; 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): + if not self.should_use_dyndeps_for_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' + rule_name = 'depscan' 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) @@ -907,10 +909,15 @@ int dummy; # 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. + all_suffixes = set() + for s in compilers.lang_suffixes['cpp']: + all_suffixes.add(s) + for s in compilers.lang_suffixes['fortran']: + all_suffixes.add(s) selected_sources = [] for source in compiled_sources: ext = os.path.splitext(source)[1][1:] - if ext in compilers.lang_suffixes['cpp']: + if ext in all_suffixes: selected_sources.append(source) return selected_sources @@ -1945,7 +1952,15 @@ int dummy; description = 'Compiling Swift source $in' self.add_rule(NinjaRule(rule, command, [], description)) - def generate_fortran_dep_hack(self, crstr): + def use_dyndeps_for_fortran(self) -> bool: + '''Use the new Ninja feature for scanning dependencies during build, + rather than up front. Remove this and all old scanning code once Ninja + minimum version is bumped to 1.10.''' + return mesonlib.version_compare(self.ninja_version, '>=1.10.0') + + def generate_fortran_dep_hack(self, crstr: str) -> None: + if self.use_dyndeps_for_fortran(): + return rule = 'FORTRAN_DEP_HACK{}'.format(crstr) if mesonlib.is_windows(): cmd = ['cmd', '/C'] @@ -2029,22 +2044,16 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) 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) + rulename = 'depscan' + if rulename in self.ruledict: + # Scanning command is the same for native and cross compilation. + return + command = self.environment.get_build_command() + \ + ['--internal', 'depscan'] + args = ['$picklefile', '$out', '$in'] + description = 'Module scanner.' + rule = NinjaRule(rulename, command, args, description) + self.add_rule(rule) def generate_compile_rules(self): @@ -2146,6 +2155,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) """ Find all module and submodule made available in a Fortran code file. """ + if self.use_dyndeps_for_fortran(): + return compiler = None # TODO other compilers for lang, c in self.environment.coredata.compilers.host.items(): @@ -2198,6 +2209,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) """ Find all module and submodule needed by a Fortran target """ + if self.use_dyndeps_for_fortran(): + return [] dirname = Path(self.get_target_private_dir(target)) tdeps = self.fortran_deps[target.get_basename()] @@ -2502,16 +2515,20 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if not is_generated: abs_src = Path(build_dir) / rel_src extra_deps += self.get_fortran_deps(compiler, abs_src, target) - # Dependency hack. Remove once multiple outputs in Ninja is fixed: - # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 - for modname, srcfile in self.fortran_deps[target.get_basename()].items(): - modfile = os.path.join(self.get_target_private_dir(target), - compiler.module_name_to_filename(modname)) - - if srcfile == src: - crstr = self.get_rule_suffix(target.for_machine) - depelem = NinjaBuildElement(self.all_outputs, modfile, 'FORTRAN_DEP_HACK' + crstr, rel_obj) - self.add_build(depelem) + if not self.use_dyndeps_for_fortran(): + # Dependency hack. Remove once multiple outputs in Ninja is fixed: + # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 + for modname, srcfile in self.fortran_deps[target.get_basename()].items(): + modfile = os.path.join(self.get_target_private_dir(target), + compiler.module_name_to_filename(modname)) + + if srcfile == src: + crstr = self.get_rule_suffix(target.for_machine) + depelem = NinjaBuildElement(self.all_outputs, + modfile, + 'FORTRAN_DEP_HACK' + crstr, + rel_obj) + self.add_build(depelem) commands += compiler.get_module_outdir_args(self.get_target_private_dir(target)) element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) @@ -2537,7 +2554,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 not self.should_scan_target(target): + if not self.should_use_dyndeps_for_target(target): return dep_scan_file = self.get_dep_scan_file_for(target) element.add_item('dyndep', dep_scan_file) diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index 6eebbfe..24a1f36 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -15,12 +15,23 @@ import pathlib import pickle import re +import os +import sys import typing as T from ..backend.ninjabackend import TargetDependencyScannerInfo +from ..compilers.compilers import lang_suffixes -import_re = re.compile('\w*import ([a-zA-Z0-9]+);') -export_re = re.compile('\w*export module ([a-zA-Z0-9]+);') +cpp_import_re = re.compile('\w*import ([a-zA-Z0-9]+);') +cpp_export_re = re.compile('\w*export module ([a-zA-Z0-9]+);') + +FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]" +FORTRAN_MODULE_PAT = r"^\s*\bmodule\b\s+(\w+)\s*(?:!+.*)*$" +FORTRAN_SUBMOD_PAT = r"^\s*\bsubmodule\b\s*\((\w+:?\w+)\)\s*(\w+)" +FORTRAN_USE_PAT = r"^\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)" + +fortran_module_re = re.compile(FORTRAN_MODULE_PAT) +fortran_use_re = re.compile(FORTRAN_USE_PAT) class DependencyScanner: def __init__(self, pickle_file: str, outfile: str, sources: T.List[str]): @@ -34,9 +45,19 @@ class DependencyScanner: 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) + suffix = os.path.splitext(fname)[1][1:] + if suffix in lang_suffixes['fortran']: + self.scan_fortran_file(fname) + elif suffix in lang_suffixes['cpp']: + self.scan_fortran_file(fname) + else: + sys.exit('Can not scan files with suffix .{}.'.format(suffix)) + + def scan_fortran_file(self, fname: str) -> None: + fpath = pathlib.Path(fname) + for line in fpath.read_text().split('\n'): + import_match = fortran_use_re.match(line) + export_match = fortran_module_re.match(line) if import_match: needed = import_match.group(1) if fname in self.needs: @@ -51,13 +72,37 @@ class DependencyScanner: self.provided_by[exported_module] = fname self.exports[fname] = exported_module + def scan_cpp_file(self, fname: str) -> None: + fpath = pathlib.Path(fname) + for line in fpath.read_text().split('\n'): + import_match = cpp_import_re.match(line) + export_match = cpp_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 module_name_for(self, src: str) -> str: + suffix= os.path.splitext(src)[1][1:] + if suffix in lang_suffixes['fortran']: + return os.path.join(self.target_data.private_dir, '{}.mod'.format(self.exports[src])) + elif suffix in lang_suffixes['cpp']: + return '{}.ifc'.format(self.exports[src]) + else: + raise RuntimeError('Unreachable code.') def scan(self) -> int: for s in self.sources: @@ -67,15 +112,15 @@ class DependencyScanner: for src in self.sources: objfilename = self.objname_for(src) if src in self.sources_with_exports: - ifc_entry = '| ' + self.ifcname_for(src) + ifc_entry = '| ' + self.module_name_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 + provider_modfile = self.module_name_for(provider_src) + mod_dep = '| ' + provider_modfile else: mod_dep = '' ofile.write('build {} {}: dyndep {}\n'.format(objfilename, |