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 32from .. import mlog 33 34from mesonbuild.mesonlib import ( 35 version_compare, EnvironmentException, MesonException, MachineChoice, LibType 36) 37 38if T.TYPE_CHECKING: 39 from ..envconfig import MachineInfo 40 41 42class FortranCompiler(CLikeCompiler, Compiler): 43 44 language = 'fortran' 45 46 def __init__(self, exelist, version, for_machine: MachineChoice, 47 is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): 48 Compiler.__init__(self, exelist, version, for_machine, info, **kwargs) 49 CLikeCompiler.__init__(self, is_cross, exe_wrapper) 50 self.id = 'unknown' 51 52 def has_function(self, funcname, prefix, env, *, extra_args=None, dependencies=None): 53 raise MesonException('Fortran does not have "has_function" capability.\n' 54 'It is better to test if a Fortran capability is working like:\n\n' 55 "meson.get_compiler('fortran').links('block; end block; end program')\n\n" 56 'that example is to see if the compiler has Fortran 2008 Block element.') 57 58 def sanity_check(self, work_dir: Path, environment): 59 """ 60 Check to be sure a minimal program can compile and execute 61 with this compiler & platform. 62 """ 63 work_dir = Path(work_dir) 64 source_name = work_dir / 'sanitycheckf.f90' 65 binary_name = work_dir / 'sanitycheckf' 66 if binary_name.is_file(): 67 binary_name.unlink() 68 69 source_name.write_text('print *, "Fortran compilation is working."; end') 70 71 extra_flags = [] 72 extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) 73 extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) 74 extra_flags += self.get_always_args() 75 # %% build the test executable "sanitycheckf" 76 # cwd=work_dir is necessary on Windows especially for Intel compilers to avoid error: cannot write on sanitycheckf.obj 77 # this is a defect with how Windows handles files and ifort's object file-writing behavior vis concurrent ProcessPoolExecutor. 78 # This simple workaround solves the issue. 79 # FIXME: cwd=str(work_dir) is for Python 3.5 on Windows, when 3.5 is deprcated, this can become cwd=work_dir 80 returncode = subprocess.run(self.exelist + extra_flags + [str(source_name), '-o', str(binary_name)], 81 cwd=str(work_dir)).returncode 82 if returncode != 0: 83 raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) 84 if self.is_cross: 85 if self.exe_wrapper is None: 86 # Can't check if the binaries run so we have to assume they do 87 return 88 cmdlist = self.exe_wrapper + [str(binary_name)] 89 else: 90 cmdlist = [str(binary_name)] 91 # %% Run the test executable 92 try: 93 returncode = subprocess.run(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode 94 if returncode != 0: 95 raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string()) 96 except OSError: 97 raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string()) 98 99 def get_std_warn_args(self, level): 100 return FortranCompiler.std_warn_args 101 102 def get_buildtype_args(self, buildtype): 103 return gnulike_buildtype_args[buildtype] 104 105 def get_optimization_args(self, optimization_level): 106 return gnu_optimization_args[optimization_level] 107 108 def get_debug_args(self, is_debug): 109 return clike_debug_args[is_debug] 110 111 def get_dependency_gen_args(self, outtarget, outfile): 112 return [] 113 114 def get_preprocess_only_args(self): 115 return ['-cpp'] + super().get_preprocess_only_args() 116 117 def get_module_incdir_args(self): 118 return ('-I', ) 119 120 def get_module_outdir_args(self, path): 121 return ['-module', path] 122 123 def compute_parameters_with_absolute_paths(self, parameter_list, build_dir): 124 for idx, i in enumerate(parameter_list): 125 if i[:2] == '-I' or i[:2] == '-L': 126 parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) 127 128 return parameter_list 129 130 def module_name_to_filename(self, module_name: str) -> str: 131 if '_' in module_name: # submodule 132 s = module_name.lower() 133 if self.id in ('gcc', 'intel', 'intel-cl'): 134 filename = s.replace('_', '@') + '.smod' 135 elif self.id in ('pgi', 'flang'): 136 filename = s.replace('_', '-') + '.mod' 137 else: 138 filename = s + '.mod' 139 else: # module 140 filename = module_name.lower() + '.mod' 141 142 return filename 143 144 def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED): 145 code = 'stop; end program' 146 return self.find_library_impl(libname, env, extra_dirs, code, libtype) 147 148 def has_multi_arguments(self, args: T.Sequence[str], env): 149 for arg in args[:]: 150 # some compilers, e.g. GCC, don't warn for unsupported warning-disable 151 # flags, so when we are testing a flag like "-Wno-forgotten-towel", also 152 # check the equivalent enable flag too "-Wforgotten-towel" 153 # GCC does error for "-fno-foobar" 154 if arg.startswith('-Wno-'): 155 args.append('-W' + arg[5:]) 156 if arg.startswith('-Wl,'): 157 mlog.warning('{} looks like a linker argument, ' 158 'but has_argument and other similar methods only ' 159 'support checking compiler arguments. Using them ' 160 'to check linker arguments are never supported, ' 161 'and results are likely to be wrong regardless of ' 162 'the compiler you are using. has_link_argument or ' 163 'other similar method can be used instead.' 164 .format(arg)) 165 code = 'stop; end program' 166 return self.has_arguments(args, env, code, mode='compile') 167 168 169class GnuFortranCompiler(GnuCompiler, FortranCompiler): 170 def __init__(self, exelist, version, for_machine: MachineChoice, 171 is_cross, info: 'MachineInfo', exe_wrapper=None, 172 defines=None, **kwargs): 173 FortranCompiler.__init__(self, exelist, version, for_machine, 174 is_cross, info, exe_wrapper, **kwargs) 175 GnuCompiler.__init__(self, defines) 176 default_warn_args = ['-Wall'] 177 self.warn_args = {'0': [], 178 '1': default_warn_args, 179 '2': default_warn_args + ['-Wextra'], 180 '3': default_warn_args + ['-Wextra', '-Wpedantic', '-fimplicit-none']} 181 182 def get_options(self): 183 opts = FortranCompiler.get_options(self) 184 fortran_stds = ['legacy', 'f95', 'f2003'] 185 if version_compare(self.version, '>=4.4.0'): 186 fortran_stds += ['f2008'] 187 if version_compare(self.version, '>=8.0.0'): 188 fortran_stds += ['f2018'] 189 opts.update({ 190 'std': coredata.UserComboOption( 191 'Fortran language standard to use', 192 ['none'] + fortran_stds, 193 'none', 194 ), 195 }) 196 return opts 197 198 def get_option_compile_args(self, options) -> T.List[str]: 199 args = [] 200 std = options['std'] 201 if std.value != 'none': 202 args.append('-std=' + std.value) 203 return args 204 205 def get_dependency_gen_args(self, outtarget, outfile) -> 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) -> T.List[str]: 215 return ['-lgfortran', '-lm'] 216 217 def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): 218 ''' 219 Derived from mixins/clike.py:has_header, but without C-style usage of 220 __has_include which breaks with GCC-Fortran 10: 221 https://github.com/mesonbuild/meson/issues/7017 222 ''' 223 fargs = {'prefix': prefix, 'header': hname} 224 code = '{prefix}\n#include <{header}>' 225 return self.compiles(code.format(**fargs), env, extra_args=extra_args, 226 dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) 227 228 229class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler): 230 def __init__(self, exelist, version, for_machine: MachineChoice, 231 is_cross, info: 'MachineInfo', exe_wrapper=None, 232 defines=None, **kwargs): 233 GnuFortranCompiler.__init__(self, exelist, version, for_machine, 234 is_cross, info, exe_wrapper, defines, 235 **kwargs) 236 ElbrusCompiler.__init__(self) 237 238class G95FortranCompiler(FortranCompiler): 239 240 LINKER_PREFIX = '-Wl,' 241 242 def __init__(self, exelist, version, for_machine: MachineChoice, 243 is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): 244 FortranCompiler.__init__(self, exelist, version, for_machine, 245 is_cross, info, exe_wrapper, **kwargs) 246 self.id = 'g95' 247 default_warn_args = ['-Wall'] 248 self.warn_args = {'0': [], 249 '1': default_warn_args, 250 '2': default_warn_args + ['-Wextra'], 251 '3': default_warn_args + ['-Wextra', '-pedantic']} 252 253 def get_module_outdir_args(self, path: str) -> T.List[str]: 254 return ['-fmod=' + path] 255 256 def get_no_warn_args(self): 257 # FIXME: Confirm that there's no compiler option to disable all warnings 258 return [] 259 260 261class SunFortranCompiler(FortranCompiler): 262 263 LINKER_PREFIX = '-Wl,' 264 265 def __init__(self, exelist, version, for_machine: MachineChoice, 266 is_cross, info: 'MachineInfo', exe_wrapper=None, 267 **kwargs): 268 FortranCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) 269 self.id = 'sun' 270 271 def get_dependency_gen_args(self, outtarget, outfile) -> T.List[str]: 272 return ['-fpp'] 273 274 def get_always_args(self): 275 return [] 276 277 def get_warn_args(self, level): 278 return [] 279 280 def get_module_incdir_args(self): 281 return ('-M', ) 282 283 def get_module_outdir_args(self, path: str) -> T.List[str]: 284 return ['-moddir=' + path] 285 286 def openmp_flags(self) -> T.List[str]: 287 return ['-xopenmp'] 288 289 290class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): 291 292 def __init__(self, exelist, version, for_machine: MachineChoice, 293 is_cross, info: 'MachineInfo', exe_wrapper=None, 294 **kwargs): 295 self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp') 296 FortranCompiler.__init__(self, exelist, version, for_machine, 297 is_cross, info, exe_wrapper, **kwargs) 298 # FIXME: Add support for OS X and Windows in detect_fortran_compiler so 299 # we are sent the type of compiler 300 IntelGnuLikeCompiler.__init__(self) 301 self.id = 'intel' 302 default_warn_args = ['-warn', 'general', '-warn', 'truncated_source'] 303 self.warn_args = {'0': [], 304 '1': default_warn_args, 305 '2': default_warn_args + ['-warn', 'unused'], 306 '3': ['-warn', 'all']} 307 308 def get_options(self): 309 opts = FortranCompiler.get_options(self) 310 fortran_stds = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] 311 opts.update({ 312 'std': coredata.UserComboOption( 313 'Fortran language standard to use', 314 ['none'] + fortran_stds, 315 'none', 316 ), 317 }) 318 return opts 319 320 def get_option_compile_args(self, options) -> T.List[str]: 321 args = [] 322 std = options['std'] 323 stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} 324 if std.value != 'none': 325 args.append('-stand=' + stds[std.value]) 326 return args 327 328 def get_preprocess_only_args(self) -> T.List[str]: 329 return ['-cpp', '-EP'] 330 331 def get_always_args(self): 332 """Ifort doesn't have -pipe.""" 333 val = super().get_always_args() 334 val.remove('-pipe') 335 return val 336 337 def language_stdlib_only_link_flags(self) -> T.List[str]: 338 return ['-lifcore', '-limf'] 339 340 def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: 341 return ['-gen-dep=' + outtarget, '-gen-depformat=make'] 342 343 344class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler): 345 346 file_suffixes = ['f90', 'f', 'for', 'ftn', 'fpp'] 347 always_args = ['/nologo'] 348 349 def __init__(self, exelist, version, for_machine: MachineChoice, 350 is_cross, target: str, info: 'MachineInfo', exe_wrapper=None, 351 **kwargs): 352 FortranCompiler.__init__(self, exelist, version, for_machine, 353 is_cross, info, exe_wrapper, **kwargs) 354 IntelVisualStudioLikeCompiler.__init__(self, target) 355 356 default_warn_args = ['/warn:general', '/warn:truncated_source'] 357 self.warn_args = {'0': [], 358 '1': default_warn_args, 359 '2': default_warn_args + ['/warn:unused'], 360 '3': ['/warn:all']} 361 362 def get_options(self): 363 opts = FortranCompiler.get_options(self) 364 fortran_stds = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] 365 opts.update({ 366 'std': coredata.UserComboOption( 367 'Fortran language standard to use', 368 ['none'] + fortran_stds, 369 'none', 370 ), 371 }) 372 return opts 373 374 def get_option_compile_args(self, options) -> T.List[str]: 375 args = [] 376 std = options['std'] 377 stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} 378 if std.value != 'none': 379 args.append('/stand:' + stds[std.value]) 380 return args 381 382 def get_module_outdir_args(self, path) -> T.List[str]: 383 return ['/module:' + path] 384 385 386class PathScaleFortranCompiler(FortranCompiler): 387 def __init__(self, exelist, version, for_machine: MachineChoice, 388 is_cross, info: 'MachineInfo', exe_wrapper=None, 389 **kwargs): 390 FortranCompiler.__init__(self, exelist, version, for_machine, 391 is_cross, info, exe_wrapper, **kwargs) 392 self.id = 'pathscale' 393 default_warn_args = ['-fullwarn'] 394 self.warn_args = {'0': [], 395 '1': default_warn_args, 396 '2': default_warn_args, 397 '3': default_warn_args} 398 399 def openmp_flags(self) -> T.List[str]: 400 return ['-mp'] 401 402 403class PGIFortranCompiler(PGICompiler, FortranCompiler): 404 def __init__(self, exelist, version, for_machine: MachineChoice, 405 is_cross, info: 'MachineInfo', exe_wrapper=None, 406 **kwargs): 407 FortranCompiler.__init__(self, exelist, version, for_machine, 408 is_cross, info, exe_wrapper, **kwargs) 409 PGICompiler.__init__(self) 410 411 default_warn_args = ['-Minform=inform'] 412 self.warn_args = {'0': [], 413 '1': default_warn_args, 414 '2': default_warn_args, 415 '3': default_warn_args + ['-Mdclchk']} 416 417 def language_stdlib_only_link_flags(self) -> T.List[str]: 418 return ['-lpgf90rtl', '-lpgf90', '-lpgf90_rpm1', '-lpgf902', 419 '-lpgf90rtl', '-lpgftnrtl', '-lrt'] 420 421class FlangFortranCompiler(ClangCompiler, FortranCompiler): 422 def __init__(self, exelist, version, for_machine: MachineChoice, 423 is_cross, info: 'MachineInfo', exe_wrapper=None, 424 **kwargs): 425 FortranCompiler.__init__(self, exelist, version, for_machine, 426 is_cross, info, exe_wrapper, **kwargs) 427 ClangCompiler.__init__(self, []) 428 self.id = 'flang' 429 default_warn_args = ['-Minform=inform'] 430 self.warn_args = {'0': [], 431 '1': default_warn_args, 432 '2': default_warn_args, 433 '3': default_warn_args} 434 435 def language_stdlib_only_link_flags(self) -> T.List[str]: 436 return ['-lflang', '-lpgmath'] 437 438class Open64FortranCompiler(FortranCompiler): 439 def __init__(self, exelist, version, for_machine: MachineChoice, 440 is_cross, info: 'MachineInfo', exe_wrapper=None, 441 **kwargs): 442 FortranCompiler.__init__(self, exelist, version, for_machine, 443 is_cross, info, exe_wrapper, **kwargs) 444 self.id = 'open64' 445 default_warn_args = ['-fullwarn'] 446 self.warn_args = {'0': [], 447 '1': default_warn_args, 448 '2': default_warn_args, 449 '3': default_warn_args} 450 451 def openmp_flags(self) -> T.List[str]: 452 return ['-mp'] 453 454 455class NAGFortranCompiler(FortranCompiler): 456 def __init__(self, exelist, version, for_machine: MachineChoice, 457 is_cross, info: 'MachineInfo', exe_wrapper=None, 458 **kwargs): 459 FortranCompiler.__init__(self, exelist, version, for_machine, 460 is_cross, info, exe_wrapper, **kwargs) 461 self.id = 'nagfor' 462 463 def get_warn_args(self, level): 464 return [] 465 466 def get_module_outdir_args(self, path) -> T.List[str]: 467 return ['-mdir', path] 468 469 def openmp_flags(self) -> T.List[str]: 470 return ['-openmp'] 471