Skip to content

Commit

Permalink
Update build system to handle environment variables at compilation ti…
Browse files Browse the repository at this point in the history
…me on a per-dep level

Removed many unused compilation utilities: call_linker, call_compiler, get_compiler_flags, get_linker_flags
Renamed many compilation methods to use 'build' in place of 'compile' to reflect multiple build methods
Moved multi-stage build into dependency class
Change generated include file to be per-interface library
Added flag to cmake file for yggdrasil to prevent it from being used in cmake tests
Added version_regex for more standardized treatment of tool versions
Allow executable to be passed to get_executable_command
Include version in tool comparison
Clean up ygginfo when R not installed or Matlab not configured
  • Loading branch information
langmm committed May 9, 2024
1 parent 7cedd06 commit dc4a347
Show file tree
Hide file tree
Showing 27 changed files with 4,783 additions and 4,598 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ __pycache__/
*.dll
*.dylib
yggdrasil/languages/C/stdint.h
yggdrasil/languages/C/*.cmake
yggdrasil/languages/CPP/*.cmake
yggdrasil/languages/fortran/*.cmake

# R build things
yggdrasil/languages/R/..Rcheck
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# YGGDRASIL CMAKELISTS
cmake_minimum_required(VERSION 3.16)

cmake_path(
Expand Down
14 changes: 12 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,8 @@ def scripts(testdir):
script_list = [
('c', ['gcc_model.c', 'hellofunc.c']),
('c++', ['gcc_model.cpp', 'hellofunc.c']),
('make', 'gcc_model'),
('cmake', 'gcc_model'),
('make', 'gcc_model.c'),
('cmake', 'gcc_model.c'),
('matlab', 'matlab_model.m'),
('matlab_error', 'matlab_error_model.m'),
('python', 'python_model.py'),
Expand Down Expand Up @@ -1887,3 +1887,13 @@ def geom_dict():
'faces': np.array([[0, 0, 7, 0, 1, 2, 3],
[1, 2, 6, 4, 5, 6, 7],
[2, 3, 5, 5, 6, 7, 4]], 'int32').T}


@pytest.fixture
def temporary_products():
from yggdrasil import tools
products = tools.IntegrationPathSet()
try:
yield products
finally:
products.teardown()
34 changes: 22 additions & 12 deletions tests/drivers/test_BuildModelDriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,8 @@ class TestBuildModelDriver(base_class):
parametrize_language = []

test_build = None
test_call_linker = None
test_parse_arguments = None
test_get_tool = None
test_get_dependency_info = None
test_get_dependency_source = None
test_get_dependency_object = None
test_get_dependency_library = None
test_get_dependency_include_dirs = None
test_get_dependency_order = None
test_invalid_function_param = None
test_compilers = None
test_compile_model = None # TODO: Verify
test_get_linker_flags = None # TODO: Verify

@pytest.fixture
def sourcedir(self, source):
Expand All @@ -33,13 +22,34 @@ def sourcedir(self, source):
@pytest.fixture
def target(self, source):
r"""Make target that should be used."""
return os.path.basename(source[0])
return os.path.basename(os.path.splitext(source[0])[0])

@pytest.fixture
def buildfile(self, sourcedir, python_class):
r"""Build file path."""
return os.path.join(sourcedir, python_class.buildfile_base)

@pytest.fixture
def builddir(self):
r"""Build file path."""
return None

@pytest.fixture
def instance_args(self, name, target):
r"""Arguments for a new instance of the tested class."""
return (name, target)

@pytest.fixture
def instance_kwargs(self, testing_options, timeout, working_dir,
polling_interval, namespace, source,
buildfile, builddir):
r"""Keyword arguments for a new instance of the tested class."""
return dict(testing_options.get('kwargs', {}),
yml={'working_dir': working_dir},
timeout=timeout, sleeptime=polling_interval,
namespace=namespace, buildfile=buildfile,
builddir=builddir, remove_products=True)

def test_get_language_for_source(self, python_class, source):
r"""Test the get_language_for_source method."""
buildfile = None
Expand Down
132 changes: 1 addition & 131 deletions tests/drivers/test_CMakeModelDriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,15 @@
from tests.drivers.test_BuildModelDriver import (
TestBuildModelDriver as base_class)
import os
import re
import pprint
import tempfile
from yggdrasil import platform
from yggdrasil.drivers.CMakeModelDriver import (
CMakeModelDriver, CMakeConfigure, CMakeBuilder)
from yggdrasil.drivers.CModelDriver import GCCCompiler
from yggdrasil.drivers.CPPModelDriver import CPPModelDriver


@pytest.mark.related_language('cmake')
def test_CMakeConfigure(scripts):
r"""Test CMakeConfigure."""
src = scripts['c'][0]
sourcedir = os.path.dirname(src)
builddir = sourcedir
# Test get_output_file
out = CMakeConfigure.get_output_file(src, dont_build=True)
assert out == builddir
out = CMakeConfigure.get_output_file(src, dont_build=True,
builddir='.', working_dir=sourcedir)
assert out == builddir
# Test get_flags
out_A = CMakeConfigure.get_flags(dont_link=True)
out_B = CMakeConfigure.get_flags(dont_link=True, outfile='.')
assert out_A == out_B
Expand All @@ -36,84 +22,16 @@ def test_CMakeBuilder(scripts):
src = scripts['c'][0]
target = os.path.splitext(os.path.basename(src))[0]
builddir = os.path.dirname(src)
obj = os.path.splitext(src)[0] + '.obj'
out = os.path.splitext(src)[0]
if platform._is_win: # pragma: windows
out += '.exe'
# Test get_output_file
assert CMakeBuilder.get_output_file(obj) == out
assert CMakeBuilder.get_output_file(obj, target='clean') == 'clean'
assert CMakeBuilder.get_output_file(builddir, target=target) == out
with pytest.raises(RuntimeError):
CMakeBuilder.get_output_file(builddir)
# Test get_flags
out_A = CMakeBuilder.get_flags(target=target, working_dir=builddir)
out_B = CMakeBuilder.get_flags(target=target,
outfile=os.path.join('.', os.path.basename(out)))
assert out_A == out_B


@pytest.mark.language('cmake')
def test_create_include():
r"""Test create_include."""
target = 'target'
tempdir = tempfile.gettempdir()
fname_dll = os.path.join(tempdir, 'test.dll')
fname_lib = os.path.join(tempdir, 'test.lib')
for fname in [fname_dll, fname_lib]:
with open(fname, 'w') as fd:
fd.write('')
assert os.path.isfile(fname)
testlist = [(['-DYGG'], [], ['ADD_DEFINITIONS(-DYGG)']),
(['-Wall'], [], ['ADD_DEFINITIONS(-Wall)']),
(['/nologo'], [], ['ADD_DEFINITIONS(/nologo)']),
(['-Iinclude_dir'], [], ['INCLUDE_DIRECTORIES(include_dir)']),
([], ['-lm'], ['TARGET_LINK_LIBRARIES(%s -lm)' % target]),
([], ['-Llib_dir'], ['LINK_DIRECTORIES(lib_dir)']),
([], ['/LIBPATH:"lib_dir"'], ['LINK_DIRECTORIES(lib_dir)']),
([], ['m'], ['TARGET_LINK_LIBRARIES(%s m)' % target])]
if CMakeConfigure.add_libraries: # pragma: debug
testlist += [([], [fname_dll], ['ADD_LIBRARY(test SHARED IMPORTED)']),
([], [fname_lib], ['ADD_LIBRARY(test STATIC IMPORTED)'])]
else:
tempdir_cp = tempdir
if platform._is_win: # pragma: windows
tempdir_cp = tempdir.replace('\\', re.escape('\\'))
testlist += [([], [fname_dll], [('FIND_LIBRARY(TEST_LIBRARY NAMES %s '
'test HINTS %s)')
% (os.path.basename(fname_dll), tempdir_cp)]),
([], [fname_lib], [('FIND_LIBRARY(TEST_LIBRARY NAMES %s '
'test HINTS %s)')
% (os.path.basename(fname_lib), tempdir_cp)])]
from yggdrasil.drivers.CModelDriver import CModelDriver
CModelDriver.compile_dependencies()
CMakeModelDriver.compile_dependencies()
kws = {'compiler': CModelDriver.get_tool('compiler'),
'linker': CModelDriver.get_tool('linker')}
for c, l, lines in testlist:
out = CMakeConfigure.create_include(None, target, compiler_flags=c,
linker_flags=l, verbose=True,
**kws)
for x in lines:
try:
assert x in out
except AssertionError: # pragma: debug
print("Could not find '%s':" % x)
pprint.pprint(out)
raise
for fname in [fname_dll, fname_lib]:
os.remove(fname)
with pytest.raises(ValueError):
CMakeConfigure.create_include(
None, target, compiler_flags=['invalid'], **kws)
with pytest.raises(ValueError):
CMakeConfigure.create_include(
None, target, linker_flags=['-invalid'], **kws)
with pytest.raises(ValueError):
CMakeConfigure.create_include(
None, target, linker_flags=['/invalid'], **kws)


@pytest.mark.absent_language('cmake')
def test_CMakeModelDriver_no_C_library(scripts): # pragma: windows
r"""Test CMakeModelDriver error when C library not installed."""
Expand All @@ -127,7 +45,7 @@ def test_CMakeModelDriver_error_cmake(scripts):
makedir, target = os.path.split(scripts['cmake'])
with pytest.raises(RuntimeError):
CMakeModelDriver('test', target,
sourcedir=makedir, compiler_flags='-P',
sourcedir=makedir, configurer_flags='-P',
target_language='c',
overwrite=True, remove_products=True)

Expand Down Expand Up @@ -168,16 +86,6 @@ def dont_verify_fds(self, verify_count_fds, disable_verify_count_fds):
r"""Turn off verification, fds linger on windows."""
yield

@pytest.fixture
def instance_kwargs(self, testing_options, timeout, sourcedir,
polling_interval, namespace, source):
r"""Keyword arguments for a new instance of the tested class."""
return dict(testing_options.get('kwargs', {}),
yml={'working_dir': sourcedir},
timeout=timeout, sleeptime=polling_interval,
namespace=namespace, env_compiler='CXX',
env_compiler_flags='CXXFLAGS')

@pytest.mark.skipif(not platform._is_win, reason="Windows only.")
@pytest.mark.skipif(not GCCCompiler.is_installed(),
reason="GNU compiler not installed.")
Expand All @@ -191,18 +99,6 @@ def test_sbdir(self, instance, sourcedir, builddir):
assert instance.sourcedir == sourcedir
assert instance.builddir == builddir

def test_write_wrappers(self, instance):
r"""Test write_wrappers method with verbosity and existing
include file."""
try:
instance.overwrite = False
instance.write_wrappers(verbose=False)
instance.write_wrappers(verbose=True)
instance.overwrite = True
instance.write_wrappers(verbose=True)
finally:
instance.overwrite = True


class TestCMakeModelDriver_wd(TestCMakeModelDriver):
r"""Test runner for CMakeModelDriver with working directory."""
Expand All @@ -219,29 +115,3 @@ def instance_kwargs(self, testing_options, timeout, sourcedir,
sourcedir='.', builddir='build',
compiler_flags=['-Wdev'], skip_compiler=True,
remove_products=True)

# Disable instance args?

# @pytest.mark.skip(reason="determine what lines this is testing")
def test_call_compiler(self, python_class, instance):
r"""Test call_compiler without full path."""
# instance.cleanup()
CPPModelDriver.compile_dependencies()
python_class.call_compiler(instance.source_files,
builddir='build',
working_dir=instance.working_dir,
dont_build=True)
out = instance.model_file
if platform._is_win:
out = os.path.join(os.path.dirname(out),
'Debug',
os.path.basename(out))
compiler = CPPModelDriver.get_tool('compiler')
print("COMPILER", compiler.tool_version())
python_class.call_compiler(instance.source_files,
out=out,
builddir='build',
working_dir=instance.working_dir,
overwrite=True,
target_compiler=compiler.toolname,
target_linker=compiler.linker().toolname)
Loading

0 comments on commit dc4a347

Please sign in to comment.