summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2019-03-14 23:37:50 +0200
committerGitHub <noreply@github.com>2019-03-14 23:37:50 +0200
commit2b446ab53978d18898eca5661e9752bc75b0a794 (patch)
tree0b0bba8baadf9b04bed36540a7126b005564abc8
parent1f0b3652ac0abd15a8212a4cf9f3d9d5c4579c98 (diff)
parente491792a956dfae50cfae90cad7b7b6732b74c3d (diff)
downloadmeson-2b446ab53978d18898eca5661e9752bc75b0a794.tar.gz
Merge pull request #5081 from scivision/fortraninclude
FEATURE: Fortran Include directive
-rw-r--r--docs/markdown/snippets/fortran-include.md12
-rw-r--r--mesonbuild/backend/ninjabackend.py143
-rw-r--r--test cases/fortran/15 include/inc1.f905
-rw-r--r--test cases/fortran/15 include/inc2.f902
-rw-r--r--test cases/fortran/15 include/main.f908
-rw-r--r--test cases/fortran/15 include/meson.build4
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 000000000..a811765b6
--- /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 7225b4220..21c6c08ea 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 000000000..0aec9ba25
--- /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 000000000..065b9903d
--- /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 000000000..661aa62c2
--- /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 000000000..560912881
--- /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)