Skip to content

Commit

Permalink
Merge pull request #552 from dstadelm/compile_only_required_subset
Browse files Browse the repository at this point in the history
  • Loading branch information
kraigher committed Oct 4, 2019
2 parents 1eaaba0 + 0a54b58 commit c0cd9c2
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 7 deletions.
22 changes: 22 additions & 0 deletions vunit/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,28 @@ def _get_affected_files_in_compile_order(self, target_files, get_depend_func):
affected_files = self._get_affected_files(target_files, get_depend_func)
return self._get_compile_order(affected_files, get_depend_func.__self__)

def get_minimal_file_set_in_compile_order(self, target_files=None):
"""
Get the minimal set of files to be compiled for a list of target files of type SourceFile
param: target_files: List of type SourceFile, if the paramater is None all files are used
"""
###
# First get all files that are required to fullfill the dependencies for the target files
dependency_graph = self.create_dependency_graph(True)
dependency_files = self._get_affected_files(target_files or self.get_source_files_in_order(),
dependency_graph.get_dependencies)

###
# Now the file set is known, but it has to be evaluated which files
# realy have to be compiled according to their timestamp.
max_file_set_to_be_compiled = self.get_files_in_compile_order(incremental=True, files=dependency_files)

# get_files_in_compile_order returns more files than actually are in the
# list of dependent files. So the list is filtered for only the files
# that are required
min_file_set_to_be_compiled = [f for f in max_file_set_to_be_compiled if f in dependency_files]
return min_file_set_to_be_compiled

def _get_affected_files(self, target_files, get_depend_func):
"""
Get affected files given a list of type SourceFile, if the list is None
Expand Down
15 changes: 11 additions & 4 deletions vunit/simulator_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,14 @@ def add_simulator_specific(self, project):
Hook for the simulator interface to add simulator specific things to the project
"""

def compile_project(self, project, printer=NO_COLOR_PRINTER, continue_on_error=False):
def compile_project(self, project, printer=NO_COLOR_PRINTER, continue_on_error=False, target_files=None):
"""
Compile the project
param: target_files: Given a list of SourceFiles only these and dependent files are compiled
"""
self.add_simulator_specific(project)
self.setup_library_mapping(project)
self.compile_source_files(project, printer, continue_on_error)
self.compile_source_files(project, printer, continue_on_error, target_files=target_files)

def simulate(self, output_path, test_suite_name, config, elaborate_only):
"""
Expand Down Expand Up @@ -214,13 +215,19 @@ def __compile_source_file(self, source_file, printer):

return True

def compile_source_files(self, project, printer=NO_COLOR_PRINTER, continue_on_error=False):
def compile_source_files(self, project, printer=NO_COLOR_PRINTER, continue_on_error=False, target_files=None):
"""
Use compile_source_file_command to compile all source_files
param: target_files: Given a list of SourceFiles only these and dependent files are compiled
"""
dependency_graph = project.create_dependency_graph()
failures = []
source_files = project.get_files_in_compile_order(dependency_graph=dependency_graph)

if target_files is None:
source_files = project.get_files_in_compile_order(dependency_graph=dependency_graph)
else:
source_files = project.get_minimal_file_set_in_compile_order(target_files)

source_files_to_skip = set()

max_library_name = 0
Expand Down
24 changes: 24 additions & 0 deletions vunit/test/unit/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,30 @@ def test_finds_component_instantiation_dependencies(self):
self.assertIn(comp1_arch, dependencies)
self.assertIn(comp2, dependencies)

def test_get_minimal_file_set_in_compile_order_without_target(self):
self.create_dummy_three_file_project()
deps = self.project.get_minimal_file_set_in_compile_order()
self.assertEqual(len(deps), 3)
self.assertTrue(deps[0] == self.project.get_source_files_in_order()[0])
self.assertTrue(deps[1] == self.project.get_source_files_in_order()[1])
self.assertTrue(deps[2] == self.project.get_source_files_in_order()[2])

def test_get_minimal_file_set_in_compile_order_with_target(self):
self.create_dummy_three_file_project()
deps = self.project.get_minimal_file_set_in_compile_order(
target_files=[self.project.get_source_files_in_order()[1]])
self.assertEqual(len(deps), 2)
self.assertTrue(deps[0] == self.project.get_source_files_in_order()[0])
self.assertTrue(deps[1] == self.project.get_source_files_in_order()[1])

# To test that indirect dependencies are included
deps = self.project.get_dependencies_in_compile_order(
target_files=[self.project.get_source_files_in_order()[2]])
self.assertEqual(len(deps), 3)
self.assertTrue(deps[0] == self.project.get_source_files_in_order()[0])
self.assertTrue(deps[1] == self.project.get_source_files_in_order()[1])
self.assertTrue(deps[2] == self.project.get_source_files_in_order()[2])

def test_get_dependencies_in_compile_order_without_target(self):
self.create_dummy_three_file_project()
deps = self.project.get_dependencies_in_compile_order()
Expand Down
15 changes: 15 additions & 0 deletions vunit/test/unit/test_simulator_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ def test_compile_source_files(self):
""")
self.assertEqual(project.get_files_in_compile_order(incremental=True), [])

def test_compile_source_files_minimal_subset(self):
simif = create_simulator_interface()
project = Project()
project.add_library("lib", "lib_path")
write_file("file1.vhd", "")
file1 = project.add_source_file("file1.vhd", "lib", file_type="vhdl")

with mock.patch("vunit.project.Project.get_minimal_file_set_in_compile_order",
autospec=True) as target_function:
target_function.return_value = []
printer = MockPrinter()
simif.compile_source_files(project, printer=printer, target_files=[file1])
simif.compile_source_files(project, printer=printer, target_files=[file1])
self.assertTrue(target_function.called)

def test_compile_source_files_continue_on_error(self):
simif = create_simulator_interface()

Expand Down
6 changes: 4 additions & 2 deletions vunit/test/unit/test_test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,10 @@ def test_test_information(self, tempdir):
self.assertEqual(set(item
for test_suite in test_suites
for item in test_suite.test_information.items()),
set([("lib.tb_entity.Test 1", Test("Test 1", _file_location(file_name, 'Test 1'))),
("lib.tb_entity.Test 2", Test("Test 2", _file_location(file_name, 'Test 2')))]))
set([("lib.tb_entity.Test 1", Test("Test 1",
_file_location(file_name, 'Test 1'))),
("lib.tb_entity.Test 2", Test("Test 2",
_file_location(file_name, 'Test 2')))]))

@with_tempdir
def test_fail_on_unknown_sim_option(self, tempdir):
Expand Down
38 changes: 38 additions & 0 deletions vunit/test/unit/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,44 @@ def test_set_sim_option_before_adding_file(self):
method("ghdl.elab_flags", [], allow_empty=True)
self.assertRaises(ValueError, method, "ghdl.elab_flags", [])

def test_get_testbench_files(self):
ui = self._create_ui()
lib = ui.add_library("lib")
file_name = self.create_entity_file()
lib.add_source_file(file_name)
self.create_file('tb_ent.vhd', '''
entity tb_ent is
generic (runner_cfg : string);
end entity;
architecture a of tb_ent is
begin
main : process
begin
if run("test1") then
elsif run("test2") then
end if;
end process;
end architecture;
''')

self.create_file('tb_ent2.vhd', '''
entity tb_ent2 is
generic (runner_cfg : string);
end entity;
architecture a of tb_ent2 is
begin
end architecture;
''')
lib.add_source_file('tb_ent.vhd')
lib.add_source_file('tb_ent2.vhd')
simulator_if = ui._create_simulator_if() # pylint: disable=protected-access
target_files = ui._get_testbench_files(simulator_if) # pylint: disable=protected-access
expected = [lib.get_source_file(file_name)._source_file # pylint: disable=protected-access
for file_name in ['tb_ent2.vhd', 'tb_ent.vhd']]
self.assertEqual(sorted(target_files, key=lambda x: x.name), sorted(expected, key=lambda x: x.name))

def _create_ui(self, *args):
""" Create an instance of the VUnit public interface class """
with mock.patch("vunit.ui.SIMULATOR_FACTORY.select_simulator",
Expand Down
4 changes: 4 additions & 0 deletions vunit/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class TestSuiteWrapper(object):
def __init__(self, test_case):
self._test_case = test_case

@property
def file_name(self):
return self._test_case.file_name

@property
def test_names(self):
return [self._test_case.name]
Expand Down
8 changes: 8 additions & 0 deletions vunit/test_suites.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def __init__(self, test, config, simulator_if, elaborate_only=False):
test_suite_name=self._name,
test_cases=[test.name])

@property
def file_name(self):
return self._test.location.file_name

@property
def name(self):
return self._name
Expand Down Expand Up @@ -87,6 +91,10 @@ def __init__(self, tests, config, simulator_if, elaborate_only=False):
test_suite_name=self._name,
test_cases=[test.name for test in tests])

@property
def file_name(self):
return self._tests[0].location.file_name if self._tests else ""

@property
def test_names(self):
return [_full_name(self._name, test.name)
Expand Down
17 changes: 16 additions & 1 deletion vunit/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1039,9 +1039,24 @@ def _compile(self, simulator_if):
"""
Compile entire project
"""
# get test benches
if self._args.minimal:
target_files = self._get_testbench_files(simulator_if)
else:
target_files = None

simulator_if.compile_project(self._project,
continue_on_error=self._args.keep_compiling,
printer=self._printer)
printer=self._printer, target_files=target_files)

def _get_testbench_files(self, simulator_if):
"""
Return the list of all test bench files for the currently selected tests to run
"""
test_list = self._create_tests(simulator_if)
tb_file_names = {test_suite.file_name for test_suite in test_list}
return [self.get_source_file(file_name)._source_file # pylint: disable=protected-access
for file_name in tb_file_names]

def _run_test(self, test_cases, report):
"""
Expand Down
4 changes: 4 additions & 0 deletions vunit/vunit_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def _create_argument_parser(description=None, for_documentation=False):
default=False,
help='Only compile project without running tests')

parser.add_argument('-m', '--minimal', action='store_true',
default=False,
help='Only compile files required for the (filtered) test benches')

parser.add_argument('-k', '--keep-compiling', action='store_true',
default=False,
help='Continue compiling even after errors only skipping files that depend on failed files')
Expand Down

0 comments on commit c0cd9c2

Please sign in to comment.