1# Copyright 2012-2017 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from pathlib import Path
16import typing as T
17import subprocess, os
18
19from .. import coredata
20from .compilers import (
21    clike_debug_args,
22    Compiler,
23)
24from .mixins.clike import CLikeCompiler
25from .mixins.gnu import (
26    GnuCompiler, gnulike_buildtype_args, gnu_optimization_args,
27)
28from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler
29from .mixins.clang import ClangCompiler
30from .mixins.elbrus import ElbrusCompiler
31from .mixins.pgi import PGICompiler
32
33from mesonbuild.mesonlib import (
34    version_compare, EnvironmentException, MesonException, MachineChoice,
35    LibType, OptionKey,
36)
37
38if T.TYPE_CHECKING:
39    from ..coredata import KeyedOptionDictType
40    from ..dependencies import Dependency
41    from ..envconfig import MachineInfo
42    from ..environment import Environment
43    from ..linkers import DynamicLinker
44    from ..programs import ExternalProgram
45    from .compilers import CompileCheckMode
46
47
48class FortranCompiler(CLikeCompiler, Compiler):
49
50    language = 'fortran'
51
52    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
53                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
54                 linker: T.Optional['DynamicLinker'] = None,
55                 full_version: T.Optional[str] = None):
56        Compiler.__init__(self, exelist, version, for_machine, info,
57                          is_cross=is_cross, full_version=full_version, linker=linker)
58        CLikeCompiler.__init__(self, exe_wrapper)
59
60    def has_function(self, funcname: str, prefix: str, env: 'Environment', *,
61                     extra_args: T.Optional[T.List[str]] = None,
62                     dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]:
63        raise MesonException('Fortran does not have "has_function" capability.\n'
64                             'It is better to test if a Fortran capability is working like:\n\n'
65                             "meson.get_compiler('fortran').links('block; end block; end program')\n\n"
66                             'that example is to see if the compiler has Fortran 2008 Block element.')
67
68    def sanity_check(self, work_dir_: str, environment: 'Environment') -> None:
69        work_dir = Path(work_dir_)
70        source_name = work_dir / 'sanitycheckf.f90'
71        binary_name = work_dir / 'sanitycheckf'
72        if binary_name.is_file():
73            binary_name.unlink()
74
75        source_name.write_text('program main; print *, "Fortran compilation is working."; end program', encoding='utf-8')
76
77        extra_flags: T.List[str] = []
78        extra_flags += environment.coredata.get_external_args(self.for_machine, self.language)
79        extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language)
80        extra_flags += self.get_always_args()
81        # %% build the test executable "sanitycheckf"
82        # cwd=work_dir is necessary on Windows especially for Intel compilers to avoid error: cannot write on sanitycheckf.obj
83        # this is a defect with how Windows handles files and ifort's object file-writing behavior vis concurrent ProcessPoolExecutor.
84        # This simple workaround solves the issue.
85        # FIXME: cwd=str(work_dir) is for Python 3.5 on Windows, when 3.5 is deprcated, this can become cwd=work_dir
86        returncode = subprocess.run(self.exelist + extra_flags + [str(source_name), '-o', str(binary_name)],
87                                    cwd=str(work_dir)).returncode
88        if returncode != 0:
89            raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string())
90        if self.is_cross:
91            if self.exe_wrapper is None:
92                # Can't check if the binaries run so we have to assume they do
93                return
94            cmdlist = self.exe_wrapper.get_command() + [str(binary_name)]
95        else:
96            cmdlist = [str(binary_name)]
97        # %% Run the test executable
98        try:
99            returncode = subprocess.run(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
100            if returncode != 0:
101                raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string())
102        except OSError:
103            raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string())
104
105    def get_buildtype_args(self, buildtype: str) -> T.List[str]:
106        return gnulike_buildtype_args[buildtype]
107
108    def get_optimization_args(self, optimization_level: str) -> T.List[str]:
109        return gnu_optimization_args[optimization_level]
110
111    def get_debug_args(self, is_debug: bool) -> T.List[str]:
112        return clike_debug_args[is_debug]
113
114    def get_preprocess_only_args(self) -> T.List[str]:
115        return ['-cpp'] + super().get_preprocess_only_args()
116
117    def get_module_incdir_args(self) -> T.Tuple[str, ...]:
118        return ('-I', )
119
120    def get_module_outdir_args(self, path: str) -> T.List[str]:
121        return ['-module', path]
122
123    def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
124                                               build_dir: str) -> T.List[str]:
125        for idx, i in enumerate(parameter_list):
126            if i[:2] == '-I' or i[:2] == '-L':
127                parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
128
129        return parameter_list
130
131    def module_name_to_filename(self, module_name: str) -> str:
132        if '_' in module_name:  # submodule
133            s = module_name.lower()
134            if self.id in ('gcc', 'intel', 'intel-cl'):
135                filename = s.replace('_', '@') + '.smod'
136            elif self.id in ('pgi', 'flang'):
137                filename = s.replace('_', '-') + '.mod'
138            else:
139                filename = s + '.mod'
140        else:  # module
141            filename = module_name.lower() + '.mod'
142
143        return filename
144
145    def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
146                     libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
147        code = 'stop; end program'
148        return self._find_library_impl(libname, env, extra_dirs, code, libtype)
149
150    def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]:
151        return self._has_multi_arguments(args, env, 'stop; end program')
152
153    def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]:
154        return self._has_multi_link_arguments(args, env, 'stop; end program')
155
156    def get_options(self) -> 'KeyedOptionDictType':
157        opts = super().get_options()
158        key = OptionKey('std', machine=self.for_machine, lang=self.language)
159        opts.update({
160            key: coredata.UserComboOption(
161                'Fortran language standard to use',
162                ['none'],
163                'none',
164            ),
165        })
166        return opts
167
168
169class GnuFortranCompiler(GnuCompiler, FortranCompiler):
170
171    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
172                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
173                 defines: T.Optional[T.Dict[str, str]] = None,
174                 linker: T.Optional['DynamicLinker'] = None,
175                 full_version: T.Optional[str] = None):
176        FortranCompiler.__init__(self, exelist, version, for_machine,
177                                 is_cross, info, exe_wrapper, linker=linker,
178                                 full_version=full_version)
179        GnuCompiler.__init__(self, defines)
180        default_warn_args = ['-Wall']
181        self.warn_args = {'0': [],
182                          '1': default_warn_args,
183                          '2': default_warn_args + ['-Wextra'],
184                          '3': default_warn_args + ['-Wextra', '-Wpedantic', '-fimplicit-none']}
185
186    def get_options(self) -> 'KeyedOptionDictType':
187        opts = FortranCompiler.get_options(self)
188        fortran_stds = ['legacy', 'f95', 'f2003']
189        if version_compare(self.version, '>=4.4.0'):
190            fortran_stds += ['f2008']
191        if version_compare(self.version, '>=8.0.0'):
192            fortran_stds += ['f2018']
193        key = OptionKey('std', machine=self.for_machine, lang=self.language)
194        opts[key].choices = ['none'] + fortran_stds
195        return opts
196
197    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
198        args = []
199        key = OptionKey('std', machine=self.for_machine, lang=self.language)
200        std = options[key]
201        if std.value != 'none':
202            args.append('-std=' + std.value)
203        return args
204
205    def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
206        # Disabled until this is fixed:
207        # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162
208        # return ['-cpp', '-MD', '-MQ', outtarget]
209        return []
210
211    def get_module_outdir_args(self, path: str) -> T.List[str]:
212        return ['-J' + path]
213
214    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
215        # We need to apply the search prefix here, as these link arguments may
216        # be passed to a differen compiler with a different set of default
217        # search paths, such as when using Clang for C/C++ and gfortran for
218        # fortran,
219        search_dir = self._get_search_dirs(env)
220        search_dirs: T.List[str] = []
221        if search_dir is not None:
222            for d in search_dir.split()[-1][len('libraries: ='):].split(':'):
223                search_dirs.append(f'-L{d}')
224        return search_dirs + ['-lgfortran', '-lm']
225
226    def has_header(self, hname: str, prefix: str, env: 'Environment', *,
227                   extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None,
228                   dependencies: T.Optional[T.List['Dependency']] = None,
229                   disable_cache: bool = False) -> T.Tuple[bool, bool]:
230        '''
231        Derived from mixins/clike.py:has_header, but without C-style usage of
232        __has_include which breaks with GCC-Fortran 10:
233        https://github.com/mesonbuild/meson/issues/7017
234        '''
235        code = f'{prefix}\n#include <{hname}>'
236        return self.compiles(code, env, extra_args=extra_args,
237                             dependencies=dependencies, mode='preprocess', disable_cache=disable_cache)
238
239
240class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler):
241    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
242                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
243                 defines: T.Optional[T.Dict[str, str]] = None,
244                 linker: T.Optional['DynamicLinker'] = None,
245                 full_version: T.Optional[str] = None):
246        GnuFortranCompiler.__init__(self, exelist, version, for_machine, is_cross,
247                                    info, exe_wrapper, defines=defines,
248                                    linker=linker, full_version=full_version)
249        ElbrusCompiler.__init__(self)
250
251class G95FortranCompiler(FortranCompiler):
252
253    LINKER_PREFIX = '-Wl,'
254
255    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
256                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
257                 linker: T.Optional['DynamicLinker'] = None,
258                 full_version: T.Optional[str] = None):
259        FortranCompiler.__init__(self, exelist, version, for_machine,
260                                 is_cross, info, exe_wrapper, linker=linker,
261                                 full_version=full_version)
262        self.id = 'g95'
263        default_warn_args = ['-Wall']
264        self.warn_args = {'0': [],
265                          '1': default_warn_args,
266                          '2': default_warn_args + ['-Wextra'],
267                          '3': default_warn_args + ['-Wextra', '-pedantic']}
268
269    def get_module_outdir_args(self, path: str) -> T.List[str]:
270        return ['-fmod=' + path]
271
272    def get_no_warn_args(self) -> T.List[str]:
273        # FIXME: Confirm that there's no compiler option to disable all warnings
274        return []
275
276
277class SunFortranCompiler(FortranCompiler):
278
279    LINKER_PREFIX = '-Wl,'
280
281    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
282                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
283                 linker: T.Optional['DynamicLinker'] = None,
284                 full_version: T.Optional[str] = None):
285        FortranCompiler.__init__(self, exelist, version, for_machine,
286                                 is_cross, info, exe_wrapper, linker=linker,
287                                 full_version=full_version)
288        self.id = 'sun'
289
290    def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
291        return ['-fpp']
292
293    def get_always_args(self) -> T.List[str]:
294        return []
295
296    def get_warn_args(self, level: str) -> T.List[str]:
297        return []
298
299    def get_module_incdir_args(self) -> T.Tuple[str, ...]:
300        return ('-M', )
301
302    def get_module_outdir_args(self, path: str) -> T.List[str]:
303        return ['-moddir=' + path]
304
305    def openmp_flags(self) -> T.List[str]:
306        return ['-xopenmp']
307
308
309class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler):
310
311    file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp', )
312
313    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
314                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
315                 linker: T.Optional['DynamicLinker'] = None,
316                 full_version: T.Optional[str] = None):
317        FortranCompiler.__init__(self, exelist, version, for_machine,
318                                 is_cross, info, exe_wrapper, linker=linker,
319                                 full_version=full_version)
320        # FIXME: Add support for OS X and Windows in detect_fortran_compiler so
321        # we are sent the type of compiler
322        IntelGnuLikeCompiler.__init__(self)
323        self.id = 'intel'
324        default_warn_args = ['-warn', 'general', '-warn', 'truncated_source']
325        self.warn_args = {'0': [],
326                          '1': default_warn_args,
327                          '2': default_warn_args + ['-warn', 'unused'],
328                          '3': ['-warn', 'all']}
329
330    def get_options(self) -> 'KeyedOptionDictType':
331        opts = FortranCompiler.get_options(self)
332        key = OptionKey('std', machine=self.for_machine, lang=self.language)
333        opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']
334        return opts
335
336    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
337        args = []
338        key = OptionKey('std', machine=self.for_machine, lang=self.language)
339        std = options[key]
340        stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'}
341        if std.value != 'none':
342            args.append('-stand=' + stds[std.value])
343        return args
344
345    def get_preprocess_only_args(self) -> T.List[str]:
346        return ['-cpp', '-EP']
347
348    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
349        # TODO: needs default search path added
350        return ['-lifcore', '-limf']
351
352    def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
353        return ['-gen-dep=' + outtarget, '-gen-depformat=make']
354
355
356class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler):
357
358    file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp', )
359    always_args = ['/nologo']
360
361    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
362                 is_cross: bool, info: 'MachineInfo', target: str,
363                 exe_wrapper: T.Optional['ExternalProgram'] = None,
364                 linker: T.Optional['DynamicLinker'] = None,
365                 full_version: T.Optional[str] = None):
366        FortranCompiler.__init__(self, exelist, version, for_machine,
367                                 is_cross, info, exe_wrapper, linker=linker,
368                                 full_version=full_version)
369        IntelVisualStudioLikeCompiler.__init__(self, target)
370
371        default_warn_args = ['/warn:general', '/warn:truncated_source']
372        self.warn_args = {'0': [],
373                          '1': default_warn_args,
374                          '2': default_warn_args + ['/warn:unused'],
375                          '3': ['/warn:all']}
376
377    def get_options(self) -> 'KeyedOptionDictType':
378        opts = FortranCompiler.get_options(self)
379        key = OptionKey('std', machine=self.for_machine, lang=self.language)
380        opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']
381        return opts
382
383    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
384        args = []
385        key = OptionKey('std', machine=self.for_machine, lang=self.language)
386        std = options[key]
387        stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'}
388        if std.value != 'none':
389            args.append('/stand:' + stds[std.value])
390        return args
391
392    def get_module_outdir_args(self, path: str) -> T.List[str]:
393        return ['/module:' + path]
394
395
396class PathScaleFortranCompiler(FortranCompiler):
397
398    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
399                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
400                 linker: T.Optional['DynamicLinker'] = None,
401                 full_version: T.Optional[str] = None):
402        FortranCompiler.__init__(self, exelist, version, for_machine,
403                                 is_cross, info, exe_wrapper, linker=linker,
404                                 full_version=full_version)
405        self.id = 'pathscale'
406        default_warn_args = ['-fullwarn']
407        self.warn_args = {'0': [],
408                          '1': default_warn_args,
409                          '2': default_warn_args,
410                          '3': default_warn_args}
411
412    def openmp_flags(self) -> T.List[str]:
413        return ['-mp']
414
415
416class PGIFortranCompiler(PGICompiler, FortranCompiler):
417
418    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
419                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
420                 linker: T.Optional['DynamicLinker'] = None,
421                 full_version: T.Optional[str] = None):
422        FortranCompiler.__init__(self, exelist, version, for_machine,
423                                 is_cross, info, exe_wrapper, linker=linker,
424                                 full_version=full_version)
425        PGICompiler.__init__(self)
426
427        default_warn_args = ['-Minform=inform']
428        self.warn_args = {'0': [],
429                          '1': default_warn_args,
430                          '2': default_warn_args,
431                          '3': default_warn_args + ['-Mdclchk']}
432
433    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
434        # TODO: needs default search path added
435        return ['-lpgf90rtl', '-lpgf90', '-lpgf90_rpm1', '-lpgf902',
436                '-lpgf90rtl', '-lpgftnrtl', '-lrt']
437
438
439class NvidiaHPC_FortranCompiler(PGICompiler, FortranCompiler):
440
441    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
442                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
443                 linker: T.Optional['DynamicLinker'] = None,
444                 full_version: T.Optional[str] = None):
445        FortranCompiler.__init__(self, exelist, version, for_machine,
446                                 is_cross, info, exe_wrapper, linker=linker,
447                                 full_version=full_version)
448        PGICompiler.__init__(self)
449
450        self.id = 'nvidia_hpc'
451        default_warn_args = ['-Minform=inform']
452        self.warn_args = {'0': [],
453                          '1': default_warn_args,
454                          '2': default_warn_args,
455                          '3': default_warn_args + ['-Mdclchk']}
456
457
458class FlangFortranCompiler(ClangCompiler, FortranCompiler):
459
460    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
461                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
462                 linker: T.Optional['DynamicLinker'] = None,
463                 full_version: T.Optional[str] = None):
464        FortranCompiler.__init__(self, exelist, version, for_machine,
465                                 is_cross, info, exe_wrapper, linker=linker,
466                                 full_version=full_version)
467        ClangCompiler.__init__(self, {})
468        self.id = 'flang'
469        default_warn_args = ['-Minform=inform']
470        self.warn_args = {'0': [],
471                          '1': default_warn_args,
472                          '2': default_warn_args,
473                          '3': default_warn_args}
474
475    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
476        # We need to apply the search prefix here, as these link arguments may
477        # be passed to a differen compiler with a different set of default
478        # search paths, such as when using Clang for C/C++ and gfortran for
479        # fortran,
480        # XXX: Untested....
481        search_dir = self._get_search_dirs(env)
482        search_dirs: T.List[str] = []
483        if search_dir is not None:
484            for d in search_dir.split()[-1][len('libraries: ='):].split(':'):
485                search_dirs.append(f'-L{d}')
486        return search_dirs + ['-lflang', '-lpgmath']
487
488class Open64FortranCompiler(FortranCompiler):
489
490    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
491                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
492                 linker: T.Optional['DynamicLinker'] = None,
493                 full_version: T.Optional[str] = None):
494        FortranCompiler.__init__(self, exelist, version, for_machine,
495                                 is_cross, info, exe_wrapper, linker=linker,
496                                 full_version=full_version)
497        self.id = 'open64'
498        default_warn_args = ['-fullwarn']
499        self.warn_args = {'0': [],
500                          '1': default_warn_args,
501                          '2': default_warn_args,
502                          '3': default_warn_args}
503
504    def openmp_flags(self) -> T.List[str]:
505        return ['-mp']
506
507
508class NAGFortranCompiler(FortranCompiler):
509
510    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
511                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
512                 linker: T.Optional['DynamicLinker'] = None,
513                 full_version: T.Optional[str] = None):
514        FortranCompiler.__init__(self, exelist, version, for_machine,
515                                 is_cross, info, exe_wrapper, linker=linker,
516                                 full_version=full_version)
517        self.id = 'nagfor'
518        # Warnings are on by default; -w disables (by category):
519        self.warn_args = {
520            '0': ['-w=all'],
521            '1': [],
522            '2': [],
523            '3': [],
524        }
525
526    def get_always_args(self) -> T.List[str]:
527        return self.get_nagfor_quiet(self.version)
528
529    def get_module_outdir_args(self, path: str) -> T.List[str]:
530        return ['-mdir', path]
531
532    @staticmethod
533    def get_nagfor_quiet(version: str) -> T.List[str]:
534        return ['-quiet'] if version_compare(version, '>=7100') else []
535
536    def get_pic_args(self) -> T.List[str]:
537        return ['-PIC']
538
539    def get_preprocess_only_args(self) -> T.List[str]:
540        return ['-fpp']
541
542    def get_std_exe_link_args(self) -> T.List[str]:
543        return self.get_always_args()
544
545    def openmp_flags(self) -> T.List[str]:
546        return ['-openmp']
547