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