Repository: mesonbuild/meson

Issue:
Fortran build does not add static library directory as include directory
Hello,

I started experimenting with Fortran builds and I tried to create a static library used from the main program. The compilation of main program fails, because it cannot find the mod file(s) of the static library. I assume this is because the compilation command does not define the static library directory as include directory (-I option with gfortran).

(Note that I may be completely wrong in my assumptions, because I am just starting with both Fortran and meson.)

I have the the following files all in the same directory:
- meson.build
- main.f95
- static_hello.f95
#### meson.build:

```
project('try-static-library', 'fortran')
static_hello = static_library('static_hello', 'static_hello.f95')
executable('test_exe', 'main.f95', link_with : static_hello)
```
#### main.f95

```
program hello
  use static_hello
  implicit none

  call static_say_hello()
end program hello
```
#### static_hello.f95

```
module static_hello
  implicit none

  private
  public :: static_say_hello

  interface static_say_hello
     module procedure say_hello
  end interface static_say_hello

contains

  subroutine say_hello
    print *, "Hello World!"
  end subroutine say_hello

end module static_hello
```

Output that shows the error:

```
> ninja
[3/4] Compiling fortran object test_exe@exe\main.f95.obj
FAILED: gfortran "-pipe" "-Wall" "-g" "-Itest_exe@exe" "-I.." "-I." "-Jtest_exe@exe"  -o test_exe@exe\main.f95.obj -c ..\main.f95
..\main.f95:2.6:

  use static_hello
      1
Fatal Error: Can't open module file 'static_hello.mod' for reading at (1): No such file or directory
[3/4] Static linking library static_hello.lib
ninja: build stopped: subcommand failed.
```

If I add the "-Istatic_hello@sta" option manually to test_exe@exe\main.f95.obj build rule in build.ninja, the build works. Hmmm... it actually works only "usually". It seems that there should also be some kind of dependency so that the static library module would always get built first. Now the build fails, if the main is built first (which is understandable, because it requires the mod file, and that is produced by the later build). But that may be a separate issue.

I am using meson from the latest master commit (e6a868e), running on Windows 10, and using GNU Fortran (tdm64-1) 4.9.2.

Thanks,

-Marko



Discussion:

Resolving patch:
diff --git a/backends.py b/backends.py
--- a/backends.py
+++ b/backends.py
@@ -220,6 +220,12 @@ def generate_basic_compiler_args(self, target, compiler):
             if isinstance(target, build.Executable):
                 commands += dep.get_exe_args()
 
+        # Fortran rquires extra include directives.
+        if compiler.language == 'fortran':
+            for lt in target.link_targets:
+                priv_dir = os.path.join(lt.subdir, lt.get_basename() + lt.type_suffix())
+                incflag = compiler.get_include_args(priv_dir)
+                commands += incflag
         return commands
 
     def build_target_link_arguments(self, compiler, deps):
diff --git a/ninjabackend.py b/ninjabackend.py
--- a/ninjabackend.py
+++ b/ninjabackend.py
@@ -1225,11 +1225,24 @@ def generate_single_compile(self, target, outfile, src, is_generated=False, head
             element.add_orderdep(d)
         element.add_orderdep(pch_dep)
         element.add_orderdep(extra_orderdeps)
+        for i in self.get_fortran_orderdeps(target, compiler):
+            element.add_orderdep(i)
         element.add_item('DEPFILE', dep_file)
         element.add_item('ARGS', commands)
         element.write(outfile)
         return rel_obj
 
+    # Fortran is a bit weird (again). When you link against a library, just compiling a source file
+    # requires the mod files that are output when single files are built. To do this right we would need to
+    # scan all inputs and write out explicit deps for each file. That is stoo slow and too much effort so
+    # instead just have an ordered dependendy on the library. This ensures all reqquired mod files are created.
+    # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that
+    # produce incorrect dep files but such is life.
+    def get_fortran_orderdeps(self, target, compiler):
+        if compiler.language != 'fortran':
+            return []
+        return [os.path.join(lt.subdir, lt.filename) for lt in target.link_targets]
+
     def generate_msvc_pch_command(self, target, compiler, pch):
         if len(pch) != 2:
             raise RuntimeError('MSVC requires one header and one source to produce precompiled headers.')


Test patch:
diff --git a/test cases/fortran/5 static/main.f95 b/test cases/fortran/5 static/main.f95
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/5 static/main.f95	
@@ -0,0 +1,6 @@
+program hello
+  use static_hello
+  implicit none
+
+  call static_say_hello()
+end program hello
diff --git a/test cases/fortran/5 static/meson.build b/test cases/fortran/5 static/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/5 static/meson.build	
@@ -0,0 +1,5 @@
+project('try-static-library', 'fortran')
+
+static_hello = static_library('static_hello', 'static_hello.f95')
+
+executable('test_exe', 'main.f95', link_with : static_hello)
diff --git a/test cases/fortran/5 static/static_hello.f95 b/test cases/fortran/5 static/static_hello.f95
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/5 static/static_hello.f95	
@@ -0,0 +1,17 @@
+module static_hello
+  implicit none
+
+  private
+  public :: static_say_hello
+
+  interface static_say_hello
+     module procedure say_hello
+  end interface static_say_hello
+
+contains
+
+  subroutine say_hello
+    print *, "Static library called."
+  end subroutine say_hello
+
+end module static_hello