Repository: mesonbuild/meson

Issue:
 Language fortran does not support library finding
The title summarises the issue.

I'm attaching a meson.build file and hello.f95.

Test system: FreeBSD 10.3-RELEASE-p11, i386, meson-0.41.1

Invocation: LD_LIBRARY_PATH=/usr/local/lib/gcc5 meson ..

[fortran.tar.gz](https://github.com/mesonbuild/meson/files/1131565/fortran.tar.gz)






Discussion:
Huh, that should have worked. I guess no one tried it before. I can reproduce it:

```
Meson encountered an error in file meson.build, line 7, column 0:
Language fortran does not support library finding.
```

Resolving patch:
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py
--- a/mesonbuild/compilers/c.py
+++ b/mesonbuild/compilers/c.py
@@ -808,12 +808,11 @@ def get_library_naming(self, env, libtype, strict=False):
             raise AssertionError('BUG: unknown libtype {!r}'.format(libtype))
         return prefixes, suffixes
 
-    def find_library(self, libname, env, extra_dirs, libtype='default'):
+    def find_library_impl(self, libname, env, extra_dirs, code, libtype='default'):
         # These libraries are either built-in or invalid
         if libname in self.ignore_libs:
             return []
         # First try if we can just add the library as -l.
-        code = 'int main(int argc, char **argv) { return 0; }'
         if extra_dirs and isinstance(extra_dirs, str):
             extra_dirs = [extra_dirs]
         # Gcc + co seem to prefer builtin lib dirs to -L dirs.
@@ -838,6 +837,10 @@ def find_library(self, libname, env, extra_dirs, libtype='default'):
                         return [trial]
         return None
 
+    def find_library(self, libname, env, extra_dirs, libtype='default'):
+        code = 'int main(int argc, char **argv) { return 0; }'
+        return self.find_library_impl(libname, env, extra_dirs, code, libtype)
+
     def thread_flags(self, env):
         if for_haiku(self.is_cross, env):
             return []
diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py
--- a/mesonbuild/compilers/fortran.py
+++ b/mesonbuild/compilers/fortran.py
@@ -14,174 +14,207 @@
 
 import os.path, subprocess
 
-from ..mesonlib import EnvironmentException, is_osx
-
+from .c import CCompiler
 from .compilers import (
-    GCC_CYGWIN,
-    GCC_MINGW,
-    GCC_OSX,
-    GCC_STANDARD,
     ICC_STANDARD,
-    apple_buildtype_linker_args,
-    get_gcc_soname_args,
-    gnulike_buildtype_args,
-    gnulike_buildtype_linker_args,
     Compiler,
+    GnuCompiler,
     ElbrusCompiler,
     IntelCompiler,
 )
 
 class FortranCompiler(Compiler):
+    library_dirs_cache = CCompiler.library_dirs_cache
+
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
         self.language = 'fortran'
-        super().__init__(exelist, version, **kwargs)
-        self.is_cross = is_cross
-        self.exe_wrapper = exe_wrapper
-        # Not really correct but I don't have Fortran compilers to test with. Sorry.
-        self.gcc_type = GCC_STANDARD
-        self.id = "IMPLEMENTATION CLASSES MUST SET THIS"
+        Compiler.__init__(self, exelist, version, **kwargs)
+        cc = CCompiler(exelist, version, is_cross, exe_wrapper, **kwargs)
+        self.id = 'unknown'
+        self.is_cross = cc.is_cross
+        self.exe_wrapper = cc.exe_wrapper
 
-    def name_string(self):
-        return ' '.join(self.exelist)
+    def get_display_language(self):
+        return 'Fortran'
 
-    def get_pic_args(self):
-        if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX):
-            return [] # On Window and OS X, pic is always on.
-        return ['-fPIC']
+    def needs_static_linker(self):
+        return CCompiler.needs_static_linker(self)
 
-    def get_std_shared_lib_link_args(self):
-        return ['-shared']
+    def get_always_args(self):
+        return CCompiler.get_always_args(self)
 
-    def needs_static_linker(self):
-        return True
+    def get_linker_debug_crt_args(self):
+        return CCompiler.get_linker_debug_crt_args(self)
 
-    def sanity_check(self, work_dir, environment):
-        source_name = os.path.join(work_dir, 'sanitycheckf.f90')
-        binary_name = os.path.join(work_dir, 'sanitycheckf')
-        with open(source_name, 'w') as ofile:
-            ofile.write('''program prog
-     print *, "Fortran compilation is working."
-end program prog
-''')
-        extra_flags = self.get_cross_extra_flags(environment, link=True)
-        pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
-        pc.wait()
-        if pc.returncode != 0:
-            raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string())
-        if self.is_cross:
-            if self.exe_wrapper is None:
-                # Can't check if the binaries run so we have to assume they do
-                return
-            cmdlist = self.exe_wrapper + [binary_name]
-        else:
-            cmdlist = [binary_name]
-        pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-        pe.wait()
-        if pe.returncode != 0:
-            raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string())
-
-    def get_std_warn_args(self, level):
-        return FortranCompiler.std_warn_args
-
-    def get_buildtype_args(self, buildtype):
-        return gnulike_buildtype_args[buildtype]
-
-    def get_buildtype_linker_args(self, buildtype):
-        if is_osx():
-            return apple_buildtype_linker_args[buildtype]
-        return gnulike_buildtype_linker_args[buildtype]
+    def get_no_stdinc_args(self):
+        return CCompiler.get_no_stdinc_args(self)
 
-    def split_shlib_to_parts(self, fname):
-        return os.path.dirname(fname), fname
+    def get_no_stdlib_link_args(self):
+        return CCompiler.get_no_stdlib_link_args(self)
+
+    def get_warn_args(self, level):
+        return CCompiler.get_warn_args(self, level)
+
+    def get_no_warn_args(self):
+        return CCompiler.get_no_warn_args(self)
 
     def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module):
-        return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module)
+        return CCompiler.get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module)
+
+    def split_shlib_to_parts(self, fname):
+        return CCompiler.split_shlib_to_parts(self, fname)
+
+    def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
+        return CCompiler.build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
 
     def get_dependency_gen_args(self, outtarget, outfile):
-        # Disabled until this is fixed:
-        # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162
-        # return ['-cpp', '-MD', '-MQ', outtarget]
         return []
 
-    def get_output_args(self, target):
-        return ['-o', target]
+    def depfile_for_object(self, objfile):
+        return CCompiler.depfile_for_object(self, objfile)
+
+    def get_depfile_suffix(self):
+        return CCompiler.get_depfile_suffix(self)
+
+    def get_exelist(self):
+        return CCompiler.get_exelist(self)
+
+    def get_linker_exelist(self):
+        return CCompiler.get_linker_exelist(self)
 
     def get_preprocess_only_args(self):
-        return ['-E']
+        return ['-cpp'] + CCompiler.get_preprocess_only_args(self)
 
     def get_compile_only_args(self):
-        return ['-c']
+        return CCompiler.get_compile_only_args(self)
 
-    def get_linker_exelist(self):
-        return self.exelist[:]
+    def get_no_optimization_args(self):
+        return CCompiler.get_no_optimization_args(self)
+
+    def get_compiler_check_args(self):
+        return CCompiler.get_compiler_check_args(self)
+
+    def get_output_args(self, target):
+        return CCompiler.get_output_args(self, target)
 
     def get_linker_output_args(self, outputname):
-        return ['-o', outputname]
+        return CCompiler.get_linker_output_args(self, outputname)
+
+    def get_coverage_args(self):
+        return CCompiler.get_coverage_args(self)
+
+    def get_coverage_link_args(self):
+        return CCompiler.get_coverage_link_args(self)
+
+    def get_werror_args(self):
+        return CCompiler.get_werror_args(self)
+
+    def get_std_exe_link_args(self):
+        return CCompiler.get_std_exe_link_args(self)
 
     def get_include_args(self, path, is_system):
-        return ['-I' + path]
+        return CCompiler.get_include_args(self, path, is_system)
 
     def get_module_incdir_args(self):
         return ('-I', )
 
     def get_module_outdir_args(self, path):
-        return ['-J' + path]
+        return ['-module' + path]
 
-    def depfile_for_object(self, objfile):
-        return objfile + '.' + self.get_depfile_suffix()
+    def module_name_to_filename(self, module_name):
+        return module_name.lower() + '.mod'
 
-    def get_depfile_suffix(self):
-        return 'd'
+    def get_std_shared_lib_link_args(self):
+        return CCompiler.get_std_shared_lib_link_args(self)
 
-    def get_std_exe_link_args(self):
-        return []
+    def get_library_dirs_real(self):
+        return CCompiler.get_library_dirs_real(self)
 
-    def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
-        return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
+    def get_library_dirs(self):
+        return CCompiler.get_library_dirs(self)
 
-    def module_name_to_filename(self, module_name):
-        return module_name.lower() + '.mod'
+    def get_pic_args(self):
+        return CCompiler.get_pic_args(self)
 
-    def get_warn_args(self, level):
-        return ['-Wall']
+    def name_string(self):
+        return CCompiler.name_string(self)
 
-    def get_no_warn_args(self):
-        return ['-w']
+    def get_linker_search_args(self, dirname):
+        return CCompiler.get_linker_search_args(self, dirname)
 
+    def get_default_include_dirs(self):
+        return CCompiler.get_default_include_dirs(self)
 
-class GnuFortranCompiler(FortranCompiler):
-    def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwargs)
-        self.gcc_type = gcc_type
-        self.defines = defines or {}
-        self.id = 'gcc'
+    def gen_export_dynamic_link_args(self, env):
+        return CCompiler.gen_export_dynamic_link_args(self, env)
 
-    def has_builtin_define(self, define):
-        return define in self.defines
+    def gen_import_library_args(self, implibname):
+        return CCompiler.gen_import_library_args(self, implibname)
 
-    def get_builtin_define(self, define):
-        if define in self.defines:
-            return self.defines[define]
+    def sanity_check(self, work_dir, environment):
+        code = '''program main
+            integer :: ret = 0
+            call exit(ret)
+        end program main'''
+        return CCompiler.sanity_check_impl(self, work_dir, environment, 'sanitycheckf.f90', code)
 
-    def get_always_args(self):
-        return ['-pipe']
+    def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
+        return CCompiler._get_compiler_check_args(self, env, extra_args, dependencies, mode='compile')
 
-    def get_coverage_args(self):
-        return ['--coverage']
+    def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
+        return CCompiler.compiles(self, code, env, extra_args, dependencies, mode)
 
-    def get_coverage_link_args(self):
-        return ['--coverage']
+    def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False):
+        return CCompiler._build_wrapper(self, code, env, extra_args, dependencies, mode, want_output)
 
-    def gen_import_library_args(self, implibname):
-        """
-        The name of the outputted import library
+    def links(self, code, env, extra_args=None, dependencies=None):
+        return CCompiler.links(self, code, env, extra_args, dependencies)
 
-        Used only on Windows
-        """
-        return ['-Wl,--out-implib=' + implibname]
+    def run(self, code, env, extra_args=None, dependencies=None):
+        return CCompiler.run(self, code, env, extra_args, dependencies)
 
-    def openmp_flags(self):
-        return ['-fopenmp']
+    def get_library_naming(self, env, libtype, strict=False):
+        return CCompiler.get_library_naming(self, env, libtype, strict)
+
+    def find_library(self, libname, env, extra_dirs, libtype='default'):
+        code = '''program main
+            call exit(0)
+        end program main'''
+        return CCompiler.find_library_impl(self, libname, env, extra_dirs, code, libtype)
+
+    def thread_flags(self, env):
+        return CCompiler.thread_flags(self, env)
+
+    def thread_link_flags(self, env):
+        return CCompiler.thread_link_flags(self, env)
+
+    def linker_to_compiler_args(self, args):
+        return CCompiler.linker_to_compiler_args(self, args)
+
+    def has_arguments(self, args, env, code, mode):
+        return CCompiler.has_arguments(self, args, env, code, mode)
+
+    def has_multi_arguments(self, args, env):
+        return CCompiler.has_multi_arguments(self, args, env)
+
+
+class GnuFortranCompiler(GnuCompiler, FortranCompiler):
+    def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
+        GnuCompiler.__init__(self, gcc_type, defines)
+        default_warn_args = ['-Wall']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args + ['-Wextra'],
+                          '3': default_warn_args + ['-Wextra', '-Wpedantic']}
+
+    def get_dependency_gen_args(self, outtarget, outfile):
+        # Disabled until this is fixed:
+        # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162
+        # return ['-cpp', '-MD', '-MQ', outtarget]
+        return []
+
+    def get_module_outdir_args(self, path):
+        return ['-J' + path]
 
     def language_stdlib_only_link_flags(self):
         return ['-lgfortran', '-lm', '-lquadmath']
@@ -194,31 +227,24 @@ def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defin
 
 class G95FortranCompiler(FortranCompiler):
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'g95'
+        default_warn_args = ['-Wall']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args + ['-Wextra'],
+                          '3': default_warn_args + ['-Wextra', '-pedantic']}
 
     def get_module_outdir_args(self, path):
         return ['-fmod=' + path]
 
-    def get_always_args(self):
-        return ['-pipe']
-
     def get_no_warn_args(self):
         # FIXME: Confirm that there's no compiler option to disable all warnings
         return []
 
-    def gen_import_library_args(self, implibname):
-        """
-        The name of the outputted import library
-
-        Used only on Windows
-        """
-        return ['-Wl,--out-implib=' + implibname]
-
 
 class SunFortranCompiler(FortranCompiler):
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'sun'
 
     def get_dependency_gen_args(self, outtarget, outfile):
@@ -241,8 +267,6 @@ def openmp_flags(self):
 
 
 class IntelFortranCompiler(IntelCompiler, FortranCompiler):
-    std_warn_args = ['-warn', 'all']
-
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
         self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp')
         FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
@@ -250,47 +274,40 @@ def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
         # we are sent the type of compiler
         IntelCompiler.__init__(self, ICC_STANDARD)
         self.id = 'intel'
+        default_warn_args = ['-warn', 'general', '-warn', 'truncated_source']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args + ['-warn', 'unused'],
+                          '3': ['-warn', 'all']}
 
-    def get_module_outdir_args(self, path):
-        return ['-module', path]
-
-    def get_warn_args(self, level):
-        return IntelFortranCompiler.std_warn_args
+    def get_preprocess_only_args(self):
+        return ['-cpp', '-EP']
 
 
 class PathScaleFortranCompiler(FortranCompiler):
-    std_warn_args = ['-fullwarn']
-
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'pathscale'
-
-    def get_module_outdir_args(self, path):
-        return ['-module', path]
-
-    def get_std_warn_args(self, level):
-        return PathScaleFortranCompiler.std_warn_args
+        default_warn_args = ['-fullwarn']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args,
+                          '3': default_warn_args}
 
     def openmp_flags(self):
         return ['-mp']
 
 
 class PGIFortranCompiler(FortranCompiler):
-    std_warn_args = ['-Minform=inform']
-
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'pgi'
+        default_warn_args = ['-Minform=inform']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args,
+                          '3': default_warn_args}
 
     def get_module_incdir_args(self):
         return ('-module', )
 
-    def get_module_outdir_args(self, path):
-        return ['-module', path]
-
-    def get_warn_args(self, level):
-        return PGIFortranCompiler.std_warn_args
-
     def get_no_warn_args(self):
         return ['-silent']
 
@@ -299,34 +316,28 @@ def openmp_flags(self):
 
 
 class Open64FortranCompiler(FortranCompiler):
-    std_warn_args = ['-fullwarn']
-
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'open64'
-
-    def get_module_outdir_args(self, path):
-        return ['-module', path]
-
-    def get_warn_args(self, level):
-        return Open64FortranCompiler.std_warn_args
+        default_warn_args = ['-fullwarn']
+        self.warn_args = {'1': default_warn_args,
+                          '2': default_warn_args,
+                          '3': default_warn_args}
 
     def openmp_flags(self):
         return ['-mp']
 
 
 class NAGFortranCompiler(FortranCompiler):
-    std_warn_args = []
-
     def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
-        super().__init__(exelist, version, is_cross, exe_wrapper=None, **kwags)
+        FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
         self.id = 'nagfor'
 
+    def get_warn_args(self, level):
+        return []
+
     def get_module_outdir_args(self, path):
         return ['-mdir', path]
 
-    def get_warn_args(self, level):
-        return NAGFortranCompiler.std_warn_args
-
     def openmp_flags(self):
         return ['-openmp']


Test patch:
diff --git a/test cases/fortran/10 find library/gzip.f90 b/test cases/fortran/10 find library/gzip.f90
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/10 find library/gzip.f90	
@@ -0,0 +1,32 @@
+module gzip
+
+  interface
+     function gzopen(path, mode) bind(C)
+       use iso_c_binding, only: c_char, c_ptr
+       implicit none
+       character(c_char), intent(in) :: path(*), mode(*)
+       type(c_ptr) :: gzopen
+     end function gzopen
+  end interface
+
+  interface
+     function gzwrite(file, buf, len) bind(C)
+       use iso_c_binding, only: c_int, c_ptr
+       implicit none
+       type(c_ptr), value, intent(in) :: file
+       type(*), intent(in) :: buf
+       integer(c_int), value, intent(in) :: len
+       integer(c_int) :: gzwrite
+     end function gzwrite
+  end interface
+
+  interface
+     function gzclose(file) bind(C)
+       use iso_c_binding, only: c_int, c_ptr
+       implicit none
+       type(c_ptr), value, intent(in) :: file
+       integer(c_int) :: gzclose
+     end function gzclose
+  end interface
+
+end module gzip
diff --git a/test cases/fortran/10 find library/main.f90 b/test cases/fortran/10 find library/main.f90
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/10 find library/main.f90	
@@ -0,0 +1,40 @@
+program main
+
+  use iso_c_binding, only: c_int, c_char, c_null_char, c_ptr
+  use gzip, only: gzopen, gzwrite, gzclose
+
+  implicit none
+
+  character(kind=c_char,len=*), parameter :: path = &
+       c_char_"test.gz"//c_null_char
+  character(kind=c_char,len=*), parameter :: mode = &
+       c_char_"wb9"//c_null_char
+  integer(c_int), parameter :: buffer_size = 512
+
+  type(c_ptr) :: file
+  character(len=buffer_size) :: buffer
+  integer(c_int) :: ret
+  integer :: i
+
+  ! open file
+  file = gzopen(path, mode)
+
+  ! fill buffer with data
+  do i=1,buffer_size/4
+     write(buffer(4*(i-1)+1:4*i), '(i3.3, a)') i, new_line('')
+  end do
+  ret = gzwrite(file, buffer, buffer_size)
+  if (ret.ne.buffer_size) then
+     write(*,'(a, i3, a, i3, a)') 'Error: ', ret, ' / ', buffer_size, &
+          ' bytes written.'
+     stop 1
+  end if
+
+  ! close file
+  ret = gzclose(file)
+  if (ret.ne.0) then
+     print *, 'Error: failure to close file with error code ', ret
+     stop 1
+  end if
+
+end program main
diff --git a/test cases/fortran/10 find library/meson.build b/test cases/fortran/10 find library/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/fortran/10 find library/meson.build	
@@ -0,0 +1,9 @@
+project('find fortran library', 'fortran')
+
+fortranc = meson.get_compiler('fortran')
+
+sources = ['main.f90', 'gzip.f90']
+zlib = fortranc.find_library('z')
+
+exe = executable('zlibtest', sources, dependencies : zlib)
+test('testzlib', exe)