Skip to content

Commit

Permalink
#3 Add specific C- and Fortran-compiler classes. Updated tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
hiker committed Apr 16, 2024
1 parent c13476e commit c980171
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 82 deletions.
3 changes: 2 additions & 1 deletion source/fab/newtools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
'''

from fab.newtools.categories import Categories
from fab.newtools.compiler import Compiler, Gcc, Gfortran, Icc, Ifort
from fab.newtools.compiler import (CCompiler, Compiler, FortranCompiler, Gcc,
Gfortran, Icc, Ifort)
from fab.newtools.flags import Flags
from fab.newtools.preprocessor import Cpp, Fpp
from fab.newtools.tool import Tool
Expand Down
167 changes: 101 additions & 66 deletions source/fab/newtools/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,74 +21,21 @@ class Compiler(Tool):
'''

def __init__(self, name: str, exec_name: str, category: Categories,
compile_flag="-c", output_flag="-o", module_folder_flag=None,
omp_flag=None, syntax_only_flag=None):
compile_flag=None, output_flag=None, omp_flag=None):
super().__init__(name, exec_name, category)
self._version = None
self._compile_flag = compile_flag
self._output_flag = output_flag
self._module_folder_flag = module_folder_flag
self._compile_flag = compile_flag if compile_flag else "-c"
self._output_flag = output_flag if output_flag else "-o"
self._omp_flag = omp_flag
self._syntax_only_flag = syntax_only_flag
self._module_output_path = ""

@property
def has_syntax_only(self):
return self._syntax_only_flag is not None

def set_module_output_path(self, path):
path = str(path)
self._module_output_path = path

def _remove_managed_flags(self, flags: List[str]):
'''Removes all flags in `flags` that will be managed by FAB.
This is atm only the module output path. The list will be
modified in-place.
:param flags: the list of flags from which to remove managed flags.
'''
i = 0
flag_len = len(self._module_folder_flag)
while i < len(flags):
flag = flags[i]
# "-J/tmp" and "-J /tmp" are both accepted.
# First check for two parameter, i.e. with space after the flag
if flag == self._module_folder_flag:
if i + 1 == len(flags):
# We have a flag, but no path. Issue a warning:
self.logger.warning(f"Flags '{' '. join(flags)} contain "
f"module path "
f"'{self._module_folder_flag}' but "
f"no path.")
break
# Delete the two arguments: flag and path
del flags[i:i+2]
continue
if flag[:flag_len] == self._module_folder_flag:
# No space between flag and path, remove this one argument
del flags[i]
continue
i += 1

def compile_file(self, input_file: Path, output_file: Path,
add_flags: List[str] = None,
syntax_only: bool = False):
add_flags: List[str] = None):
# Do we need to remove compile flag or module_folder_flag from
# add_flags??
params = [input_file.name, self._compile_flag,
self._output_flag, str(output_file)]
if syntax_only and self._syntax_only_flag:
params.append(self._syntax_only_flag)
if add_flags:
# Don't modify the user's list:
new_flags = add_flags[:]
self._remove_managed_flags(new_flags)
params += new_flags

# Append module output path
if self._module_folder_flag:
params.append(self._module_folder_flag)
params.append(self._module_output_path)
params += add_flags

return self.run(cwd=input_file.parent,
additional_parameters=params)
Expand Down Expand Up @@ -142,38 +89,126 @@ def get_version(self):


# ============================================================================
class Gcc(Compiler):
class CCompiler(Compiler):
'''This is the base class for a C compiler. It just sets the category
of the compiler as convenience.
'''

def __init__(self, name: str, exec_name: str, compile_flag=None,
output_flag=None, omp_flag=None):
super().__init__(name, exec_name, Categories.C_COMPILER,
compile_flag, output_flag, omp_flag)


# ============================================================================
class FortranCompiler(Compiler):
'''This is the base class for a Fortran compiler. It is a compiler
that needs to support a module output path and support for syntax-only
compilation (which will only generate the .mod files).
'''

def __init__(self, name: str, exec_name: str,
module_folder_flag: str, syntax_only_flag=None,
compile_flag=None, output_flag=None, omp_flag=None):

super().__init__(name, exec_name, Categories.FORTRAN_COMPILER,
compile_flag, output_flag, omp_flag)
self._module_folder_flag = module_folder_flag
self._module_output_path = ""
self._syntax_only_flag = syntax_only_flag

@property
def has_syntax_only(self):
return self._syntax_only_flag is not None

def set_module_output_path(self, path):
path = str(path)
self._module_output_path = path

def _remove_managed_flags(self, flags: List[str]):
'''Removes all flags in `flags` that will be managed by FAB.
This is atm only the module output path. The list will be
modified in-place.
:param flags: the list of flags from which to remove managed flags.
'''
i = 0
flag_len = len(self._module_folder_flag)
while i < len(flags):
flag = flags[i]
# "-J/tmp" and "-J /tmp" are both accepted.
# First check for two parameter, i.e. with space after the flag
if flag == self._module_folder_flag:
if i + 1 == len(flags):
# We have a flag, but no path. Issue a warning:
self.logger.warning(f"Flags '{' '. join(flags)} contain "
f"module path "
f"'{self._module_folder_flag}' but "
f"no path.")
break
# Delete the two arguments: flag and path
del flags[i:i+2]
continue
if flag[:flag_len] == self._module_folder_flag:
# No space between flag and path, remove this one argument
del flags[i]
continue
i += 1

def compile_file(self, input_file: Path, output_file: Path,
add_flags: List[str] = None,
syntax_only: bool = False):
params = []
if add_flags:
# Don't modify the user's list:
new_flags = add_flags[:]
# Remove any module output path that the user might specify
self._remove_managed_flags(new_flags)
params += new_flags

if syntax_only and self._syntax_only_flag:
params.append(self._syntax_only_flag)

# Append module output path
if self._module_folder_flag:
params.append(self._module_folder_flag)
params.append(self._module_output_path)
super().compile_file(input_file, output_file, params)


# ============================================================================
class Gcc(CCompiler):
'''Class for GNU's gcc compiler.
'''
def __init__(self):
super().__init__("gcc", "gcc", Categories.C_COMPILER)
super().__init__("gcc", "gcc", omp_flag="-fopenmp")


# ============================================================================
class Gfortran(Compiler):
class Gfortran(FortranCompiler):
'''Class for GNU's gfortran compiler.
'''
def __init__(self):
super().__init__("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
super().__init__("gfortran", "gfortran",
module_folder_flag="-J",
omp_flag="-fopenmp",
syntax_only_flag="-fsyntax-only")


# ============================================================================
class Icc(Compiler):
class Icc(CCompiler):
'''Class for the Intel's icc compiler.
'''
def __init__(self):
super().__init__("icc", "icc", Categories.C_COMPILER)
super().__init__("icc", "icc", omp_flag="-qopenmp")


# ============================================================================
class Ifort(Compiler):
class Ifort(FortranCompiler):
'''Class for Intel's ifort compiler.
'''
def __init__(self):
super().__init__("ifort", "ifort", Categories.FORTRAN_COMPILER,
super().__init__("ifort", "ifort",
module_folder_flag="-module",
omp_flag="-qopenmp",
syntax_only_flag="-syntax-only")
39 changes: 24 additions & 15 deletions tests/unit_tests/tools/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,45 @@

import pytest

from fab.newtools import Categories, Compiler, Gcc, Gfortran, Icc, Ifort
from fab.newtools import (Categories, CCompiler, Compiler, FortranCompiler,
Gcc, Gfortran, Icc, Ifort)


def test_compiler():
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER)
cc = CCompiler("gcc", "gcc")
cc.get_version = mock.Mock(return_value="123")
assert cc.get_version() == "123"
assert cc.category == Categories.C_COMPILER

fc = FortranCompiler("gfortran", "gfortran", "-J")
fc.get_version = mock.Mock(return_value="123")
assert fc.get_version() == "123"
assert fc.category == Categories.FORTRAN_COMPILER


def test_compiler_syntax_only():
'''Tests handling of syntax only flags.'''
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER)
fc = FortranCompiler("gfortran", "gfortran", "-J")
assert not fc.has_syntax_only
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
syntax_only_flag=None)
fc = FortranCompiler("gfortran", "gfortran", "-J", syntax_only_flag=None)
assert not fc.has_syntax_only

fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
syntax_only_flag="-fsyntax-only")
fc = FortranCompiler("gfortran", "gfortran", "-J",
syntax_only_flag="-fsyntax-only")
fc.set_module_output_path("/tmp")
assert fc.has_syntax_only
assert fc._syntax_only_flag == "-fsyntax-only"
fc.run = mock.MagicMock()
fc.compile_file(Path("a.f90"), "a.o", syntax_only=True)
fc.run.assert_called_with(cwd=PosixPath('.'),
fc.run.assert_called_with(cwd=Path('.'),
additional_parameters=['a.f90', '-c', '-o',
'a.o', '-fsyntax-only'])
'a.o', '-fsyntax-only',
"-J", "/tmp"])


def test_compiler_module_output():
'''Tests handling of module output_flags.'''
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
module_folder_flag="-J")
fc = FortranCompiler("gfortran", "gfortran", module_folder_flag="-J")
fc.set_module_output_path("/module_out")
assert fc._module_output_path == "/module_out"
fc.run = mock.MagicMock()
Expand All @@ -56,8 +63,7 @@ def test_compiler_module_output():


def test_managed_flags():
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
module_folder_flag="-J")
fc = FortranCompiler("gfortran", "gfortran", "-J")
for flags, expected in [(["a", "b"], ["a", "b"]),
(["-J", "b"], []),
(["-Jb"], []),
Expand All @@ -70,8 +76,7 @@ def test_managed_flags():


def test_compile_with_add_args():
fc = Compiler("gfortran", "gfortran", Categories.FORTRAN_COMPILER,
module_folder_flag="-J")
fc = FortranCompiler("gfortran", "gfortran", module_folder_flag="-J")
fc.set_module_output_path("/module_out")
assert fc._module_output_path == "/module_out"
fc.run = mock.MagicMock()
Expand Down Expand Up @@ -237,25 +242,29 @@ def test_gcc():
'''Tests the gcc class.'''
gcc = Gcc()
assert gcc.name == "gcc"
assert isinstance(gcc, CCompiler)
assert gcc.category == Categories.C_COMPILER


def test_gfortran():
'''Tests the gfortran class.'''
gfortran = Gfortran()
assert gfortran.name == "gfortran"
assert isinstance(gfortran, FortranCompiler)
assert gfortran.category == Categories.FORTRAN_COMPILER


def test_icc():
'''Tests the icc class.'''
icc = Icc()
assert icc.name == "icc"
assert isinstance(icc, CCompiler)
assert icc.category == Categories.C_COMPILER


def test_ifort():
'''Tests the ifort class.'''
ifort = Ifort()
assert ifort.name == "ifort"
assert isinstance(ifort, FortranCompiler)
assert ifort.category == Categories.FORTRAN_COMPILER

0 comments on commit c980171

Please sign in to comment.