diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2019-03-14 23:37:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-14 23:37:50 +0200 |
commit | 2b446ab53978d18898eca5661e9752bc75b0a794 (patch) | |
tree | 0b0bba8baadf9b04bed36540a7126b005564abc8 | |
parent | 1f0b3652ac0abd15a8212a4cf9f3d9d5c4579c98 (diff) | |
parent | e491792a956dfae50cfae90cad7b7b6732b74c3d (diff) | |
download | meson-2b446ab53978d18898eca5661e9752bc75b0a794.zip meson-2b446ab53978d18898eca5661e9752bc75b0a794.tar.gz meson-2b446ab53978d18898eca5661e9752bc75b0a794.tar.bz2 |
Merge pull request #5081 from scivision/fortraninclude
FEATURE: Fortran Include directive
-rw-r--r-- | docs/markdown/snippets/fortran-include.md | 12 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 143 | ||||
-rw-r--r-- | test cases/fortran/15 include/inc1.f90 | 5 | ||||
-rw-r--r-- | test cases/fortran/15 include/inc2.f90 | 2 | ||||
-rw-r--r-- | test cases/fortran/15 include/main.f90 | 8 | ||||
-rw-r--r-- | test cases/fortran/15 include/meson.build | 4 |
6 files changed, 120 insertions, 54 deletions
diff --git a/docs/markdown/snippets/fortran-include.md b/docs/markdown/snippets/fortran-include.md new file mode 100644 index 0000000..a811765 --- /dev/null +++ b/docs/markdown/snippets/fortran-include.md @@ -0,0 +1,12 @@ +## Fortran `include` statements recursively parsed + +While non-standard and generally not recommended, some legacy Fortran programs use `include` directives to inject code inline. +Since v0.51, Meson can handle Fortran `include` directives recursively. + +DO NOT list `include` files as sources for a target, as in general their syntax is not correct as a standalone target. +In general `include` files are meant to be injected inline as if they were copy and pasted into the source file. + +`include` was never standard and was superceded by Fortran 90 `module`. + +The `include` file is only recognized by Meson if it has a Fortran file suffix, such as `.f` `.F` `.f90` `.F90` or similar. +This is to avoid deeply nested scanning of large external legacy C libraries that only interface to Fortran by `include biglib.h` or similar. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 7225b42..21c6c08 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1811,6 +1811,7 @@ rule FORTRAN_DEP_HACK%s if compiler is None: self.fortran_deps[target.get_basename()] = {} return + modre = re.compile(r"\s*\bmodule\b\s+(\w+)\s*$", re.IGNORECASE) submodre = re.compile(r"\s*\bsubmodule\b\s+\((\w+:?\w+)\)\s+(\w+)\s*$", re.IGNORECASE) module_files = {} @@ -1848,64 +1849,15 @@ rule FORTRAN_DEP_HACK%s self.fortran_deps[target.get_basename()] = module_files def get_fortran_deps(self, compiler: FortranCompiler, src: str, target) -> List[str]: - mod_files = [] - usere = re.compile(r"\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)", re.IGNORECASE) - submodre = re.compile(r"\s*\bsubmodule\b\s+\((\w+:?\w+)\)\s+(\w+)\s*$", re.IGNORECASE) + """ + Find all modules and submodules needed by a target + """ + dirname = Path(self.get_target_private_dir(target)) tdeps = self.fortran_deps[target.get_basename()] - src = Path(src) srcdir = Path(self.source_dir) - with src.open(encoding='ascii', errors='ignore') as f: - for line in f: - usematch = usere.match(line) - if usematch is not None: - usename = usematch.group(1).lower() - if usename == 'intrinsic': # this keeps the regex simpler - continue - if usename not in tdeps: - # The module is not provided by any source file. This - # is due to: - # a) missing file/typo/etc - # b) using a module provided by the compiler, such as - # OpenMP - # There's no easy way to tell which is which (that I - # know of) so just ignore this and go on. Ideally we - # would print a warning message to the user but this is - # a common occurrence, which would lead to lots of - # distracting noise. - continue - srcfile = srcdir / tdeps[usename].fname - if not srcfile.is_file(): - if srcfile.name != src.name: # generated source file - pass - else: # subproject - continue - elif srcfile.samefile(src): # self-reference - continue - - mod_name = compiler.module_name_to_filename(usename) - mod_files.append(str(dirname / mod_name)) - else: - submodmatch = submodre.match(line) - if submodmatch is not None: - parents = submodmatch.group(1).lower().split(':') - assert len(parents) in (1, 2), ( - 'submodule ancestry must be specified as' - ' ancestor:parent but Meson found {}'.parents) - for parent in parents: - if parent not in tdeps: - raise MesonException("submodule {} relies on parent module {} that was not found.".format(submodmatch.group(2).lower(), parent)) - submodsrcfile = srcdir / tdeps[parent].fname - if not submodsrcfile.is_file(): - if submodsrcfile.name != src.name: # generated source file - pass - else: # subproject - continue - elif submodsrcfile.samefile(src): # self-reference - continue - mod_name = compiler.module_name_to_filename(parent) - mod_files.append(str(dirname / mod_name)) + mod_files = _scan_fortran_file_deps(src, srcdir, dirname, tdeps, compiler) return mod_files def get_cross_stdlib_args(self, target, compiler): @@ -2797,3 +2749,86 @@ def load(build_dir): with open(filename, 'rb') as f: obj = pickle.load(f) return obj + + +def _scan_fortran_file_deps(src: str, srcdir: Path, dirname: Path, tdeps, compiler) -> List[str]: + """ + scan a Fortran file for dependencies. Needs to be distinct from target + to allow for recursion induced by `include` statements.er + + It makes a number of assumptions, including + + * `use`, `module`, `submodule` name is not on a continuation line + + Regex + ----- + + * `incre` works for `#include "foo.f90"` and `include "foo.f90"` + * `usere` works for legacy and Fortran 2003 `use` statements + * `submodre` is for Fortran >= 2008 `submodule` + """ + + incre = re.compile(r"#?include\s*['\"](\w+\.\w+)['\"]\s*$", re.IGNORECASE) + usere = re.compile(r"\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)", re.IGNORECASE) + submodre = re.compile(r"\s*\bsubmodule\b\s+\((\w+:?\w+)\)\s+(\w+)\s*$", re.IGNORECASE) + + mod_files = [] + src = Path(src) + with src.open(encoding='ascii', errors='ignore') as f: + for line in f: + # included files + incmatch = incre.match(line) + if incmatch is not None: + incfile = srcdir / incmatch.group(1) + if incfile.suffix.lower()[1:] in compiler.file_suffixes: + mod_files.extend(_scan_fortran_file_deps(incfile, srcdir, dirname, tdeps, compiler)) + # modules + usematch = usere.match(line) + if usematch is not None: + usename = usematch.group(1).lower() + if usename == 'intrinsic': # this keeps the regex simpler + continue + if usename not in tdeps: + # The module is not provided by any source file. This + # is due to: + # a) missing file/typo/etc + # b) using a module provided by the compiler, such as + # OpenMP + # There's no easy way to tell which is which (that I + # know of) so just ignore this and go on. Ideally we + # would print a warning message to the user but this is + # a common occurrence, which would lead to lots of + # distracting noise. + continue + srcfile = srcdir / tdeps[usename].fname + if not srcfile.is_file(): + if srcfile.name != src.name: # generated source file + pass + else: # subproject + continue + elif srcfile.samefile(src): # self-reference + continue + + mod_name = compiler.module_name_to_filename(usename) + mod_files.append(str(dirname / mod_name)) + else: # submodules + submodmatch = submodre.match(line) + if submodmatch is not None: + parents = submodmatch.group(1).lower().split(':') + assert len(parents) in (1, 2), ( + 'submodule ancestry must be specified as' + ' ancestor:parent but Meson found {}'.parents) + for parent in parents: + if parent not in tdeps: + raise MesonException("submodule {} relies on parent module {} that was not found.".format(submodmatch.group(2).lower(), parent)) + submodsrcfile = srcdir / tdeps[parent].fname + if not submodsrcfile.is_file(): + if submodsrcfile.name != src.name: # generated source file + pass + else: # subproject + continue + elif submodsrcfile.samefile(src): # self-reference + continue + mod_name = compiler.module_name_to_filename(parent) + mod_files.append(str(dirname / mod_name)) + return mod_files diff --git a/test cases/fortran/15 include/inc1.f90 b/test cases/fortran/15 include/inc1.f90 new file mode 100644 index 0000000..0aec9ba --- /dev/null +++ b/test cases/fortran/15 include/inc1.f90 @@ -0,0 +1,5 @@ + +real :: pi = 4.*atan(1.) +real :: tau + +include "inc2.f90" diff --git a/test cases/fortran/15 include/inc2.f90 b/test cases/fortran/15 include/inc2.f90 new file mode 100644 index 0000000..065b990 --- /dev/null +++ b/test cases/fortran/15 include/inc2.f90 @@ -0,0 +1,2 @@ + +tau = 2*pi diff --git a/test cases/fortran/15 include/main.f90 b/test cases/fortran/15 include/main.f90 new file mode 100644 index 0000000..661aa62 --- /dev/null +++ b/test cases/fortran/15 include/main.f90 @@ -0,0 +1,8 @@ + +implicit none + +include "inc1.f90" + +print *, '2*pi:', tau + +end program diff --git a/test cases/fortran/15 include/meson.build b/test cases/fortran/15 include/meson.build new file mode 100644 index 0000000..5609128 --- /dev/null +++ b/test cases/fortran/15 include/meson.build @@ -0,0 +1,4 @@ +project('Inclusive', 'fortran') + +exe = executable('incexe', 'main.f90') +test('Fortran include files', exe) |