Skip to content

Commit 0d168ad

Browse files
authored
Merge pull request #452 from vkarak/feat/autotools-build-system
[feat] Add support for Autotools-based projects
2 parents c3bbcd7 + 105026f commit 0d168ad

File tree

4 files changed

+169
-20
lines changed

4 files changed

+169
-20
lines changed

docs/advanced.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Add a configuration step before compiling the code
123123

124124
It is often the case that a configuration step is needed before compiling a code with ``make``.
125125
To address this kind of projects, ReFrame aims to offer specific abstractions for "configure-make"-style build systems.
126-
Currently, it supports only `CMake <https://cmake.org/>`__-based projects through its :class:`CMake <reframe.core.buildsystems.CMake>` build system.
126+
It supports `CMake-based <https://cmake.org/>`__ projects through the :class:`CMake <reframe.core.buildsystems.CMake>` build system, as well as `Autotools-based <https://www.gnu.org/software/automake/>`__ projects through the :class:`Autotools <reframe.core.buildsystems.Autotools>` build system.
127127

128128
For other build systems, you can achieve the same effect using the :class:`Make <reframe.core.buildsystems.Make>` build system and the :attr:`prebuild_cmd <reframe.core.pipeline.RegressionTest.prebuild_cmd>` for performing the configuration step.
129129
The following code snippet will configure a code with ``./custom_configure`` before invoking ``make``:

reframe/core/buildsystems.py

Lines changed: 107 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class Make(BuildSystem):
181181
182182
.. code::
183183
184-
make -j [N] [-f MAKEFILE] [-C SRCDIR] CC='X' CXX='X' FC='X' NVCC='X' CPPFLAGS='X' CFLAGS='X' CXXFLAGS='X' FFLAGS='X' LDFLAGS='X' OPTIONS
184+
make -j [N] [-f MAKEFILE] [-C SRCDIR] CC='X' CXX='X' FC='X' NVCC='X' CPPFLAGS='X' CFLAGS='X' CXXFLAGS='X' FCFLAGS='X' LDFLAGS='X' OPTIONS
185185
186186
The compiler and compiler flags variables will only be passed if they are
187187
not :class:`None`.
@@ -276,7 +276,7 @@ def emit_build_commands(self, environ):
276276
cmd_parts += ["CXXFLAGS='%s'" % ' '.join(cxxflags)]
277277

278278
if fflags is not None:
279-
cmd_parts += ["FFLAGS='%s'" % ' '.join(fflags)]
279+
cmd_parts += ["FCFLAGS='%s'" % ' '.join(fflags)]
280280

281281
if ldflags is not None:
282282
cmd_parts += ["LDFLAGS='%s'" % ' '.join(ldflags)]
@@ -301,7 +301,7 @@ class SingleSource(BuildSystem):
301301
the source file and pick the correct compiler.
302302
See also the :attr:`SingleSource.lang` attribute.
303303
- ``CPPFLAGS`` are the preprocessor flags and are passed to any compiler.
304-
- ``XFLAGS`` is any of ``CFLAGS``, ``CXXFLAGS`` or ``FFLAGS`` depending on
304+
- ``XFLAGS`` is any of ``CFLAGS``, ``CXXFLAGS`` or ``FCFLAGS`` depending on
305305
the programming language of the source file.
306306
- ``SRCFILE`` is the source file to be compiled.
307307
This is set up automatically by the framework.
@@ -443,17 +443,8 @@ def _guess_language(self, filename):
443443
return 'CUDA'
444444

445445

446-
class CMake(BuildSystem):
447-
"""A build system for compiling CMake-based projects.
448-
449-
This build system will emit the following commands:
450-
451-
1. Create a build directory if :attr:`builddir` is not :class:`None` and
452-
change to it.
453-
2. Invoke ``cmake`` to configure the project by setting the corresponding
454-
CMake flags for compilers and compiler flags.
455-
3. Issue ``make`` to compile the code.
456-
"""
446+
class ConfigureBasedBuildSystem(BuildSystem):
447+
"""Abstract base class for configured-based build systems."""
457448

458449
#: The top-level directory of the code.
459450
#:
@@ -476,13 +467,39 @@ class CMake(BuildSystem):
476467
#: :default: ``[]``
477468
config_opts = fields.TypedListField('config_opts', str)
478469

470+
#: Options to be passed to the subsequent ``make`` invocation.
471+
#:
472+
#: :type: :class:`list[str]`
473+
#: :default: ``[]``
474+
make_opts = fields.TypedListField('make_opts', str)
475+
476+
#: Same as for the :attr:`Make` build system.
477+
#:
478+
#: :type: integer
479+
#: :default: :class:`None`
480+
max_concurrency = fields.IntegerField('max_concurrency', allow_none=True)
481+
479482
def __init__(self):
480483
super().__init__()
481484
self.srcdir = None
482485
self.builddir = None
483486
self.config_opts = []
487+
self.make_opts = []
484488
self.max_concurrency = None
485489

490+
491+
class CMake(ConfigureBasedBuildSystem):
492+
"""A build system for compiling CMake-based projects.
493+
494+
This build system will emit the following commands:
495+
496+
1. Create a build directory if :attr:`builddir` is not :class:`None` and
497+
change to it.
498+
2. Invoke ``cmake`` to configure the project by setting the corresponding
499+
CMake flags for compilers and compiler flags.
500+
3. Issue ``make`` to compile the code.
501+
"""
502+
486503
def _combine_flags(self, cppflags, xflags):
487504
if cppflags is None:
488505
return xflags
@@ -548,9 +565,85 @@ def emit_build_commands(self, environ):
548565
if self.max_concurrency is not None:
549566
make_cmd += [str(self.max_concurrency)]
550567

568+
if self.make_opts:
569+
make_cmd += self.make_opts
570+
551571
return prepare_cmd + [' '.join(cmake_cmd), ' '.join(make_cmd)]
552572

553573

574+
class Autotools(ConfigureBasedBuildSystem):
575+
"""A build system for compiling Autotools-based projects.
576+
577+
This build system will emit the following commands:
578+
579+
1. Create a build directory if :attr:`builddir` is not :class:`None` and
580+
change to it.
581+
2. Invoke ``configure`` to configure the project by setting the corresponding
582+
flags for compilers and compiler flags.
583+
3. Issue ``make`` to compile the code.
584+
"""
585+
586+
def emit_build_commands(self, environ):
587+
prepare_cmd = []
588+
if self.srcdir:
589+
prepare_cmd += ['cd %s' % self.srcdir]
590+
591+
if self.builddir:
592+
prepare_cmd += ['mkdir -p %s' % self.builddir,
593+
'cd %s' % self.builddir]
594+
595+
if self.builddir:
596+
configure_cmd = [os.path.join(
597+
os.path.relpath('.', self.builddir), 'configure')]
598+
else:
599+
configure_cmd = ['./configure']
600+
601+
cc = self._cc(environ)
602+
cxx = self._cxx(environ)
603+
ftn = self._ftn(environ)
604+
nvcc = self._nvcc(environ)
605+
cppflags = self._cppflags(environ)
606+
cflags = self._cflags(environ)
607+
cxxflags = self._cxxflags(environ)
608+
fflags = self._fflags(environ)
609+
ldflags = self._ldflags(environ)
610+
if cc is not None:
611+
configure_cmd += ["CC='%s'" % cc]
612+
613+
if cxx is not None:
614+
configure_cmd += ["CXX='%s'" % cxx]
615+
616+
if ftn is not None:
617+
configure_cmd += ["FC='%s'" % ftn]
618+
619+
if cppflags is not None:
620+
configure_cmd += ["CPPFLAGS='%s'" % ' '.join(cppflags)]
621+
622+
if cflags is not None:
623+
configure_cmd += ["CFLAGS='%s'" % ' '.join(cflags)]
624+
625+
if cxxflags is not None:
626+
configure_cmd += ["CXXFLAGS='%s'" % ' '.join(cxxflags)]
627+
628+
if fflags is not None:
629+
configure_cmd += ["FCFLAGS='%s'" % ' '.join(fflags)]
630+
631+
if ldflags is not None:
632+
configure_cmd += ["LDFLAGS='%s'" % ' '.join(ldflags)]
633+
634+
if self.config_opts:
635+
configure_cmd += self.config_opts
636+
637+
make_cmd = ['make -j']
638+
if self.max_concurrency is not None:
639+
make_cmd += [str(self.max_concurrency)]
640+
641+
if self.make_opts:
642+
make_cmd += self.make_opts
643+
644+
return prepare_cmd + [' '.join(configure_cmd), ' '.join(make_cmd)]
645+
646+
554647
class BuildSystemField(fields.TypedField):
555648
"""A field representing a build system.
556649

reframe/core/pipeline.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,10 +930,16 @@ def compile(self, **compile_opts):
930930
if os.path.isdir(staged_sourcepath):
931931
if not self.build_system:
932932
# Try to guess the build system
933-
if os.path.exists(os.path.join(staged_sourcepath,
934-
'CMakeLists.txt')):
933+
cmakelists = os.path.join(staged_sourcepath, 'CMakeLists.txt')
934+
configure_ac = os.path.join(staged_sourcepath, 'configure.ac')
935+
configure_in = os.path.join(staged_sourcepath, 'configure.in')
936+
if os.path.exists(cmakelists):
935937
self.build_system = 'CMake'
936938
self.build_system.builddir = 'rfm_build'
939+
elif (os.path.exists(configure_ac) or
940+
os.path.exists(configure_in)):
941+
self.build_system = 'Autotools'
942+
self.build_system.builddir = 'rfm_build'
937943
else:
938944
self.build_system = 'Make'
939945

unittests/test_buildsystems.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def test_emit_from_env(self):
4848
"make -f Makefile_foo -C foodir -j 32 CC='gcc' CXX='g++' "
4949
"FC='gfortran' NVCC='nvcc' CPPFLAGS='-DNDEBUG' "
5050
"CFLAGS='-Wall -std=c99' CXXFLAGS='-Wall -std=c++11' "
51-
"FFLAGS='-Wall' LDFLAGS='-dynamic' FOO=1"
51+
"FCFLAGS='-Wall' LDFLAGS='-dynamic' FOO=1"
5252
]
5353
self.assertEqual(expected,
5454
self.build_system.emit_build_commands(self.environ))
@@ -62,7 +62,7 @@ def test_emit_from_buildsystem(self):
6262
expected = [
6363
"make -f Makefile_foo -C foodir -j CC='cc' CXX='CC' FC='ftn' "
6464
"NVCC='clang' CPPFLAGS='-DFOO' CFLAGS='-Wall -std=c99 -O3' "
65-
"CXXFLAGS='-Wall -std=c++11 -O3' FFLAGS='-Wall -O3' "
65+
"CXXFLAGS='-Wall -std=c++11 -O3' FCFLAGS='-Wall -O3' "
6666
"LDFLAGS='-static' FOO=1"
6767
]
6868
self.assertEqual(expected,
@@ -82,6 +82,7 @@ def test_emit_from_env(self):
8282
self.build_system.srcdir = 'src'
8383
self.build_system.builddir = 'build/foo'
8484
self.build_system.config_opts = ['-DFOO=1']
85+
self.build_system.make_opts = ['install']
8586
self.build_system.max_concurrency = 32
8687
expected = [
8788
"cd src",
@@ -94,7 +95,7 @@ def test_emit_from_env(self):
9495
"-DCMAKE_CXX_FLAGS='-DNDEBUG -Wall -std=c++11' "
9596
"-DCMAKE_Fortran_FLAGS='-DNDEBUG -Wall' "
9697
"-DCMAKE_EXE_LINKER_FLAGS='-dynamic' -DFOO=1 ../..",
97-
"make -j 32"
98+
"make -j 32 install"
9899

99100
]
100101
self.assertEqual(expected,
@@ -127,6 +128,55 @@ def test_emit_no_env_defaults(self):
127128
self.build_system.emit_build_commands(self.environ))
128129

129130

131+
class TestAutotools(_BuildSystemTest, unittest.TestCase):
132+
def create_build_system(self):
133+
return bs.Autotools()
134+
135+
def test_emit_from_env(self):
136+
self.build_system.srcdir = 'src'
137+
self.build_system.builddir = 'build/foo'
138+
self.build_system.config_opts = ['FOO=1']
139+
self.build_system.make_opts = ['check']
140+
self.build_system.max_concurrency = 32
141+
expected = [
142+
"cd src",
143+
"mkdir -p build/foo",
144+
"cd build/foo",
145+
"../../configure CC='gcc' CXX='g++' FC='gfortran' "
146+
"CPPFLAGS='-DNDEBUG' CFLAGS='-Wall -std=c99' "
147+
"CXXFLAGS='-Wall -std=c++11' FCFLAGS='-Wall' "
148+
"LDFLAGS='-dynamic' FOO=1",
149+
"make -j 32 check"
150+
151+
]
152+
self.assertEqual(expected,
153+
self.build_system.emit_build_commands(self.environ))
154+
155+
def test_emit_from_buildsystem(self):
156+
super().setup_base_buildsystem()
157+
self.build_system.builddir = 'build/foo'
158+
self.build_system.config_opts = ['FOO=1']
159+
self.build_system.max_concurrency = None
160+
expected = [
161+
"mkdir -p build/foo",
162+
"cd build/foo",
163+
"../../configure CC='cc' CXX='CC' FC='ftn' "
164+
"CPPFLAGS='-DFOO' CFLAGS='-Wall -std=c99 -O3' "
165+
"CXXFLAGS='-Wall -std=c++11 -O3' FCFLAGS='-Wall -O3' "
166+
"LDFLAGS='-static' FOO=1",
167+
"make -j"
168+
169+
]
170+
print(self.build_system.emit_build_commands(self.environ))
171+
self.assertEqual(expected,
172+
self.build_system.emit_build_commands(self.environ))
173+
174+
def test_emit_no_env_defaults(self):
175+
self.build_system.flags_from_environ = False
176+
self.assertEqual(['./configure', 'make -j'],
177+
self.build_system.emit_build_commands(self.environ))
178+
179+
130180
class TestSingleSource(_BuildSystemTest, unittest.TestCase):
131181
def create_build_system(self):
132182
return bs.SingleSource()

0 commit comments

Comments
 (0)