-
Notifications
You must be signed in to change notification settings - Fork 31
refactor: remove deprecated source code linker cache #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # ******************************************************************************* | ||
| # Copyright (c) 2025 Contributors to the Eclipse Foundation | ||
| # | ||
| # See the NOTICE file(s) distributed with this work for additional | ||
| # information regarding copyright ownership. | ||
| # | ||
| # This program and the accompanying materials are made available under the | ||
| # terms of the Apache License 2.0 which is available at | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # ******************************************************************************* | ||
|
|
||
| """Parse source files for traceability tags used by the Bazel link generator.""" | ||
|
|
||
| import logging | ||
| from pathlib import Path | ||
|
|
||
| from src.extensions.score_source_code_linker.needlinks import NeedLink | ||
|
|
||
| LOGGER = logging.getLogger(__name__) | ||
|
|
||
| TAGS = [ | ||
| "# " + "req-traceability:", | ||
| "# " + "req-Id:", | ||
| "// " + "req-traceability:", | ||
| "// " + "req-Id:", | ||
| ] | ||
|
|
||
|
|
||
| def _extract_references_from_line(line: str): | ||
| """Extract requirement IDs from a line containing a tag.""" | ||
|
|
||
| for tag in TAGS: | ||
| tag_index = line.find(tag) | ||
| if tag_index >= 0: | ||
| line_after_tag = line[tag_index + len(tag) :].strip() | ||
| # Split by comma or space to get multiple requirements | ||
| for req in line_after_tag.replace(",", " ").split(): | ||
| yield tag, req.strip() | ||
|
|
||
|
|
||
| def extract_references_from_file( | ||
| root: Path, file_path_name: Path, file_path: Path | ||
| ) -> list[NeedLink]: | ||
| """Scan a single file for traceability tags and return the findings. | ||
|
|
||
| ``root / file_path_name`` identifies the file to read. ``file_path`` is | ||
| the path that should be recorded in the generated source-link data, which | ||
| may differ when the input file is located below Bazel's external prefix. | ||
| """ | ||
|
|
||
| assert root.is_absolute(), "Root path must be absolute" | ||
| assert not file_path_name.is_absolute(), "File path must be relative to the root" | ||
| assert (root / file_path_name).exists(), ( | ||
| f"File {file_path_name} does not exist in root {root}." | ||
| ) | ||
|
|
||
| findings: list[NeedLink] = [] | ||
|
|
||
| try: | ||
| with open(root / file_path_name, encoding="utf-8", errors="ignore") as f: | ||
| for line_num, line in enumerate(f, 1): | ||
| for tag, req in _extract_references_from_line(line): | ||
| findings.append( | ||
| NeedLink( | ||
| file=file_path, | ||
| line=line_num, | ||
| tag=tag, | ||
| need=req, | ||
| full_line=line.strip(), | ||
| ) | ||
| ) | ||
| except (UnicodeDecodeError, PermissionError, OSError) as e: | ||
| # Skip files that can't be read as text | ||
| LOGGER.debug(f"Error reading file to parse for linked needs: \n{e}") | ||
|
|
||
| return findings |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,9 +31,6 @@ | |
| from sphinx_needs.logging import get_logger | ||
| from sphinx_needs.need_item import NeedItem | ||
|
|
||
| from src.extensions.score_source_code_linker.generate_source_code_links_json import ( | ||
| generate_source_code_links_json, | ||
| ) | ||
| from src.extensions.score_source_code_linker.helpers import get_github_link | ||
| from src.extensions.score_source_code_linker.need_source_links import ( | ||
| group_by_need, | ||
|
|
@@ -63,10 +60,7 @@ | |
| construct_and_add_need, | ||
| run_xml_parser, | ||
| ) | ||
| from src.helper_lib import ( | ||
| find_git_root, | ||
| find_ws_root, | ||
| ) | ||
| from src.helper_lib import find_ws_root | ||
|
|
||
| LOGGER = get_logger(__name__) | ||
| # Uncomment this to enable more verbose logging | ||
|
|
@@ -87,29 +81,36 @@ def get_cache_filename(build_dir: Path, filename: str) -> Path: | |
| return build_dir / filename | ||
|
|
||
|
|
||
| def build_and_save_combined_file(outdir: Path): | ||
| def build_and_save_combined_file(outdir: Path, app: Sphinx | None = None): | ||
| """ | ||
| Reads the saved partial caches of codelink & testlink | ||
| Builds the combined JSON cache & saves it | ||
| """ | ||
| source_code_links_json = os.environ.get("SCORE_SOURCELINKS") | ||
| if not source_code_links_json: | ||
| # Fallback to the obsolete way of doing source code links, | ||
| # just in case someone is not using the docs(sourcelinks=...) attribute. | ||
| # TODO: Remove this once backwards compatibility is not needed anymore. | ||
| source_code_links_json = get_cache_filename( | ||
| outdir, "score_source_code_linker_cache.json" | ||
| ) | ||
| source_code_links_path = os.environ.get("SCORE_SOURCELINKS") | ||
| if not source_code_links_path and app is not None: | ||
| source_code_links_path = str( | ||
| getattr(app.config, "score_sourcelinks_json", "") or "" | ||
| ).strip() | ||
| if source_code_links_path: | ||
| source_code_links_json = Path(source_code_links_path) | ||
| try: | ||
| source_code_links = load_source_code_links_json(source_code_links_json) | ||
| except FileNotFoundError as exc: | ||
| raise FileNotFoundError( | ||
| "Pre-generated source-code links file does not exist: " | ||
| f"{source_code_links_json}. Check SCORE_SOURCELINKS or " | ||
| "score_sourcelinks_json." | ||
| ) from exc | ||
| except AssertionError: | ||
| source_code_links = load_source_code_links_with_metadata_json( | ||
| source_code_links_json | ||
| ) | ||
| else: | ||
| source_code_links_json = Path(source_code_links_json) | ||
|
|
||
| # This isn't pretty will think of a better solution later, for now this should work | ||
| try: | ||
| source_code_links = load_source_code_links_json(source_code_links_json) | ||
| except AssertionError: | ||
| source_code_links = load_source_code_links_with_metadata_json( | ||
| source_code_links_json | ||
| LOGGER.debug( | ||
| "No pre-generated source-code links provided. Continuing without code links.", | ||
| type="score_source_code_linker", | ||
| ) | ||
| source_code_links = [] | ||
| test_cache = get_cache_filename(outdir, "score_xml_parser_cache.json") | ||
| if test_cache.exists(): | ||
| test_code_links = load_test_xml_parsed_json(test_cache) | ||
|
|
@@ -130,7 +131,7 @@ def build_and_save_combined_file(outdir: Path): | |
| # ╰──────────────────────────────────────╯ | ||
|
|
||
|
|
||
| def setup_source_code_linker(app: Sphinx, ws_root: Path | None): | ||
| def setup_source_code_linker(app: Sphinx): | ||
| """ | ||
| Setting up source_code_linker with all needed options. | ||
| Allows us to only have this run once during live_preview & esbonio | ||
|
|
@@ -164,42 +165,6 @@ def setup_source_code_linker(app: Sphinx, ws_root: Path | None): | |
| }, | ||
| ) | ||
|
|
||
| score_sourcelinks_json = os.environ.get("SCORE_SOURCELINKS") | ||
| if not score_sourcelinks_json: | ||
| score_sourcelinks_json = str( | ||
| getattr(app.config, "score_sourcelinks_json", "") | ||
| ).strip() | ||
| if score_sourcelinks_json: | ||
| # Reuse existing code paths that expect this env var. | ||
| os.environ["SCORE_SOURCELINKS"] = score_sourcelinks_json | ||
| if score_sourcelinks_json: | ||
| # No need to generate the JSON file if this env var is set | ||
| # because it points to an existing file with the needed data. | ||
| return | ||
|
|
||
| if ws_root is None: | ||
| LOGGER.info( | ||
| "No workspace root found and no SCORE_SOURCELINKS provided. " | ||
| "Skipping source-code-link scan.", | ||
| type="score_source_code_linker", | ||
| ) | ||
| return | ||
|
|
||
| scl_cache_json = get_cache_filename( | ||
| app.outdir, "score_source_code_linker_cache.json" | ||
| ) | ||
|
|
||
| if ( | ||
| not scl_cache_json.exists() | ||
| or not app.config.skip_rescanning_via_source_code_linker | ||
| ): | ||
| LOGGER.debug( | ||
| "INFO: Generating source code links JSON file.", | ||
| type="score_source_code_linker", | ||
| ) | ||
|
|
||
| generate_source_code_links_json(ws_root, scl_cache_json) | ||
|
|
||
|
|
||
| def register_test_code_linker(app: Sphinx): | ||
| # Connects function to sphinx to ensure correct execution order | ||
|
|
@@ -269,7 +234,7 @@ def setup_combined_linker(app: Sphinx, _: BuildEnvironment): | |
| "Did not find combined json 'score_scl_grouped_cache.json' in _build." | ||
| "Generating new one" | ||
| ) | ||
| build_and_save_combined_file(app.outdir) | ||
| build_and_save_combined_file(app.outdir, app) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We investigated this behavior in detail. The stale-cache observation is technically valid, but it predates this pull request and is unrelated to removing the deprecated source-code-linker cache. In live preview, Those guards are unchanged on the base branch. PR #803 only removes the extension-side generation/deletion of So this is a real cache-lifecycle issue, but not a regression introduced by this cleanup. It would be better handled as a separate follow-up, either by removing the derived JSON handoff entirely and keeping the grouped data in memory, or by adding explicit invalidation for the derived caches. |
||
|
|
||
|
|
||
| def register_repo_linker(app: Sphinx): | ||
|
|
@@ -308,26 +273,12 @@ def setup_once(app: Sphinx): | |
| # might be the only way to solve this? | ||
| if "skip_rescanning_via_source_code_linker" in app.config: | ||
| return | ||
| LOGGER.debug(f"DEBUG: Workspace root is {find_ws_root()}") | ||
| LOGGER.debug( | ||
| f"DEBUG: Current working directory is {Path('.')} = {Path('.').resolve()}" | ||
| ) | ||
| LOGGER.debug(f"DEBUG: Git root is {find_git_root()}") | ||
|
|
||
| # Run for local files if possible. In Bazel sandbox builds, ws_root may be | ||
| # unavailable; in that case we can still operate when SCORE_SOURCELINKS | ||
| # (or score_sourcelinks_json config) is provided. | ||
| ws_root = find_ws_root() | ||
| if ws_root: | ||
| # When BUILD_WORKSPACE_DIRECTORY is set, we are inside a git repository. | ||
| assert find_git_root() | ||
|
|
||
| # Register & Run (if needed) parsing & saving of JSON caches | ||
| # Note: This extension now runs on both internal and external needs_json invocations. | ||
| # Both modes aggregate links from local sources and external dependencies, enabling | ||
| # unified traceability reporting in integration repositories. Impact on external needs | ||
| # invocations is minimal since they typically don't have local test logs or source code. | ||
| setup_source_code_linker(app, ws_root) | ||
| setup_source_code_linker(app) | ||
| register_test_code_linker(app) | ||
| register_combined_linker(app) | ||
| register_repo_linker(app) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.