diff --git a/src/buildstream/_loader/loader.py b/src/buildstream/_loader/loader.py index 76b1b7649..af98504cc 100644 --- a/src/buildstream/_loader/loader.py +++ b/src/buildstream/_loader/loader.py @@ -168,6 +168,11 @@ def load(self, targets): return target_elements + def _normalize_element_name(self, filename): + # Keep equivalent relative spellings on the same loader identity path, + # so ref lookup, cache keys and workspace handling stay consistent. + return os.path.normpath(filename) + # get_loader(): # # Obtains the appropriate loader for the specified junction @@ -400,6 +405,8 @@ def _expand_link(self, path): # def _load_one_file(self, filename, provenance_node, *, load_subprojects=True): + filename = self._normalize_element_name(filename) + element = None # First check the cache, the cache might contain shallow loaded @@ -1013,10 +1020,10 @@ def _parse_name(self, name, provenance_node, *, load_subprojects=True): # to create junctions on the top level project. junction_path = name.rsplit(":", 1) if len(junction_path) == 1: - return None, junction_path[-1], self + return None, self._normalize_element_name(junction_path[-1]), self else: loader = self.get_loader(junction_path[-2], provenance_node, load_subprojects=load_subprojects) - return junction_path[-2], junction_path[-1], loader + return junction_path[-2], self._normalize_element_name(junction_path[-1]), loader # _warn(): # diff --git a/tests/frontend/show.py b/tests/frontend/show.py index e48b6a169..854dc9f3c 100644 --- a/tests/frontend/show.py +++ b/tests/frontend/show.py @@ -55,6 +55,24 @@ def test_show(cli, datafiles, target, fmt, expected): raise AssertionError("Expected output:\n{}\nInstead received output:\n{}".format(expected, result.output)) +@pytest.mark.datafiles(os.path.join(DATA_DIR, "project")) +@pytest.mark.parametrize( + "canonical,dotted", + [ + ("target.bst", "./target.bst"), + ("target.bst", "././target.bst"), + ("multiple_targets/dependency/pony.bst", "multiple_targets/./dependency/pony.bst"), + ("multiple_targets/dependency/pony.bst", "./multiple_targets/dependency/pony.bst"), + ("multiple_targets/dependency/pony.bst", "multiple_targets/dependency/./pony.bst"), + ], +) +def test_show_equivalent_target_spellings_use_same_identity(cli, datafiles, canonical, dotted): + project = str(datafiles) + + assert cli.get_element_key(project, canonical) == cli.get_element_key(project, dotted) + assert cli.get_element_state(project, canonical) == cli.get_element_state(project, dotted) + + @pytest.mark.datafiles( os.path.join( os.path.dirname(os.path.realpath(__file__)), @@ -294,6 +312,25 @@ def test_unfetched_junction(cli, tmpdir, datafiles, ref_storage, element_name, w result.assert_success() +@pytest.mark.datafiles(os.path.join(DATA_DIR, "project")) +def test_show_dot_slash_junction_uses_same_ref_lookup(cli, tmpdir, datafiles): + project = str(datafiles) + subproject_path = os.path.join(project, "files", "sub-project") + junction_path = os.path.join(project, "elements", "junction.bst") + + configure_project(project, {"ref-storage": "project.refs"}) + + ref = generate_junction(tmpdir, subproject_path, junction_path, store_ref=False) + project_refs = {"projects": {"test": {"junction.bst": [{"ref": ref}]}}} + _yaml.roundtrip_dump(project_refs, os.path.join(project, "junction.refs")) + + state = cli.get_element_state(project, "junction.bst") + dot_state = cli.get_element_state(project, "./junction.bst") + + assert state == dot_state + assert state != "no reference" + + @pytest.mark.datafiles(os.path.join(DATA_DIR, "project")) @pytest.mark.parametrize("ref_storage", [("inline"), ("project.refs")]) @pytest.mark.parametrize("workspaced", [True, False], ids=["workspace", "no-workspace"])