diff --git a/dace/codegen/compiler.py b/dace/codegen/compiler.py index d289f4bb2c..5054ccc749 100644 --- a/dace/codegen/compiler.py +++ b/dace/codegen/compiler.py @@ -656,25 +656,29 @@ def get_folder_mode(object_folder: Union[pathlib.Path, str], probe: bool = False folder_mode = F.readline().strip() return folder_mode else: - # This is to check an old style folder, i.e. a cache folder that was generated before - # the `FOLDER_MODE` file was introduced. We do some small sanity checks. + # This is to check an old style folder, i.e. a cache folder that was generated + # before the `FOLDER_MODE` file was introduced. We do some small sanity checks. + # TODO: Investigate if we should check for `program.sdfgz` and if it is not + # pressent assume that we just have some random folder. # TODO: Phase out this feature, after there are no old style caches. - found_sub_folder = False - for sub_folder in ["build", "map", "src", "include", "sample"]: - if (object_folder / sub_folder).is_dir(): - found_sub_folder = True - elif found_sub_folder: - raise NotADirectoryError(f'Expected that folder ``{object_folder}`` contains ``{sub_folder}``') - - if found_sub_folder: + maybe_an_old_style_folder = (object_folder / "build").is_dir() + for sub_folder in ["map", "src", "include", "sample"]: + if (object_folder / sub_folder).is_dir() != maybe_an_old_style_folder: + if probe: + # TODO: This is an inconsistent folder, currently it is not an error + # but should it be one? + return None + raise NotADirectoryError(f'The old-style folder ``{object_folder}`` is inconsistent.') + + if maybe_an_old_style_folder: # All expected folders where found, so expect that this is a 'development' format folder. return "development" elif probe: - # None of the files where found. Thus this is probably an empty folder that just exist. + # None of the files where found and this is probe, so it is probably just an "empty" folder. return None else: # Up for discussion what to do here. - raise NotADirectoryError(f'``{object_folder}`` does not appear to be a valid build folder.') + raise NotADirectoryError(f'``{object_folder}`` does not appear to be a valid old-style build folder.') def get_binary_name( @@ -685,15 +689,21 @@ def get_binary_name( ) -> pathlib.Path: """Returns the supposed location of the compiled library given the boundary conditions. + If folder mode is not explicitly given, then the function will use `get_folder_mode()`, + if this fails, then the `compiler.build_folder_mode` key is consulted. + :param object_folder: The build folder of the SDFG, i.e. `sdfg.build_folder`. :param sdfg_name: The name of the SDFG, i.e. `sdfg.name`. :param lib_extension: The extension of the library, i.e. file extension. If not given the config option `compiler.library_extension` is used. - :param folder_mode: The save mode for the build folder. If not given the config - option `compiler.build_folder_mode` is used. + :param folder_mode: The mode of the build folder. """ if lib_extension is None: lib_extension = Config.get('compiler', 'library_extension') + + # First try `get_folder_mode()` if that failed, consult the configuration. + if folder_mode is None: + folder_mode = get_folder_mode(object_folder, probe=True) if folder_mode is None: folder_mode = Config.get('compiler', 'build_folder_mode') diff --git a/tests/compile_folder_mode_test.py b/tests/compile_folder_mode_test.py index 80b734ae89..8d82cad841 100644 --- a/tests/compile_folder_mode_test.py +++ b/tests/compile_folder_mode_test.py @@ -5,11 +5,18 @@ import pathlib import copy import re +import tempfile import dace from dace.codegen import compiler as sdfg_compiler +@pytest.fixture +def tmp_path() -> pathlib.Path: + with tempfile.TemporaryDirectory() as tmp_dir: + yield pathlib.Path(tmp_dir) + + def _make_test_sdfg() -> dace.SDFG: sdfg = dace.SDFG("test_sdfg_" + str(uuid.uuid1()).replace("-", "_")) state = sdfg.add_state() @@ -137,7 +144,7 @@ def test_production_folder_mode(): assert not version_file.exists() with pytest.raises(NotADirectoryError, - match=re.escape(f'``{build_folder}`` does not appear to be a valid build folder.')): + match=re.escape(f'``{build_folder}`` does not appear to be a valid old-style build folder.')): sdfg_compiler.get_folder_mode(build_folder) assert sdfg_compiler.get_folder_mode(build_folder, probe=True) is None @@ -222,6 +229,83 @@ def test_build_with_scheme_one_and_then_switch(): ) +def _expected_binary_path( + build_folder: pathlib.Path, + sdfg_name: str, + folder_mode: str, + lib_extension: str, +) -> pathlib.Path: + if folder_mode == "development": + return build_folder / "build" / f"lib{sdfg_name}.{lib_extension}" + assert folder_mode == "production" + return build_folder / f"lib{sdfg_name}.{lib_extension}" + + +def _test_get_binary_name_detects_folder_mode_switch_impl( + build_folder: pathlib.Path, + version1: str, + version2: str, +) -> None: + sdfg_name = "some_sdfg" + lib_extension = "so" + build_folder.mkdir() + + with dace.config.temporary_config() as conf: + # The configuration keeps naming `version1`; after the switch only the + # `FOLDER_MODE` file knows about `version2`, so `get_binary_name()` must + # take the mode from the folder and not from the configuration. + conf.set('compiler', 'build_folder_mode', value=version1) + + sdfg_compiler.generate_program_folder(None, [], str(build_folder), folder_mode=version1) + assert sdfg_compiler.get_folder_mode(build_folder) == version1 + + lib1_path = sdfg_compiler.get_binary_name(build_folder, sdfg_name=sdfg_name, lib_extension=lib_extension) + assert lib1_path == _expected_binary_path(build_folder, sdfg_name, version1, lib_extension) + + # Now switch the folder to the second mode; the configuration still says `version1`. + sdfg_compiler.generate_program_folder(None, [], str(build_folder), folder_mode=version2) + assert sdfg_compiler.get_folder_mode(build_folder) == version2 + + lib2_path = sdfg_compiler.get_binary_name(build_folder, sdfg_name=sdfg_name, lib_extension=lib_extension) + assert lib2_path == _expected_binary_path(build_folder, sdfg_name, version2, lib_extension) + assert lib1_path != lib2_path + + +def test_get_binary_name_detects_folder_mode_switch(tmp_path): + _test_get_binary_name_detects_folder_mode_switch_impl( + build_folder=tmp_path / "dev_to_prod", + version1="development", + version2="production", + ) + _test_get_binary_name_detects_folder_mode_switch_impl( + build_folder=tmp_path / "prod_to_dev", + version1="production", + version2="development", + ) + + +def test_get_folder_mode_probes_inconsistent_old_style_folder(tmp_path): + # An old-style development folder that was generated but never compiled, i.e. + # there is no `FOLDER_MODE` file and no `build` folder. Such a folder is + # inconsistent, thus probing must return `None` and `get_binary_name()` must + # fall back to the configuration instead of raising. + build_folder = tmp_path / "old_style_never_compiled" + build_folder.mkdir() + for sub_folder in ["map", "src", "include", "sample"]: + (build_folder / sub_folder).mkdir() + + assert sdfg_compiler.get_folder_mode(build_folder, probe=True) is None + with pytest.raises(NotADirectoryError, + match=re.escape(f'The old-style folder ``{build_folder}`` is inconsistent.')): + sdfg_compiler.get_folder_mode(build_folder) + + for folder_mode in ["development", "production"]: + with dace.config.temporary_config() as conf: + conf.set('compiler', 'build_folder_mode', value=folder_mode) + lib_path = sdfg_compiler.get_binary_name(build_folder, sdfg_name="some_sdfg", lib_extension="so") + assert lib_path == _expected_binary_path(build_folder, "some_sdfg", folder_mode, "so") + + def test_already_loaded_and_comple_again(): _test_build_with_scheme_one_and_then_switch_impl( version1="development", @@ -238,3 +322,7 @@ def test_already_loaded_and_comple_again(): test_production_folder_mode() test_already_loaded_and_comple_again() test_build_with_scheme_one_and_then_switch() + with tempfile.TemporaryDirectory() as tmp_dir: + test_get_binary_name_detects_folder_mode_switch(pathlib.Path(tmp_dir)) + with tempfile.TemporaryDirectory() as tmp_dir: + test_get_folder_mode_probes_inconsistent_old_style_folder(pathlib.Path(tmp_dir))