aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2020-12-14 18:56:30 +0200
committerJussi Pakkanen <jpakkane@gmail.com>2020-12-14 18:56:30 +0200
commitd91ba1f91ca13a9616f21d626a9651a16c147186 (patch)
treefa09893cb349b0ff15cde90be8c539502efd6580
parent9f1ba4025260e620c8e49c3825ddfd51c1c4c4b6 (diff)
downloadmeson-fortranmodscan.zip
meson-fortranmodscan.tar.gz
meson-fortranmodscan.tar.bz2
Extend the C++ module scanner to handle Fortran, too.fortranmodscan
-rw-r--r--mesonbuild/backend/ninjabackend.py85
-rw-r--r--mesonbuild/scripts/depscan.py65
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,