Repository: eclipse-score/docs-as-code
Pull request #23: Fixed source_code_linker finding external needs
Author: login: MaximilianSoerenPollak
Created: 2025-05-13T12:09:10Z | Merged: 2025-05-13T14:47:28Z

Added simple loop search logic to try all available prefixes per id.


Diff:
diff --git a/BUILD b/BUILD
index 22c08c79d..951bdbf39 100644
--- a/BUILD
+++ b/BUILD
@@ -13,6 +13,8 @@
 
 load("@score_cr_checker//:cr_checker.bzl", "copyright_checker")
 
+package(default_visibility = ["//visibility:public"])
+
 copyright_checker(
     name = "copyright",
     srcs = [
diff --git a/docs/BUILD b/docs/BUILD
index 0c2ce22a7..a3b0fa5b0 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -27,7 +27,7 @@ docs(
             "suffix": "latest",  # latest main branch documentation build
             "external_needs_info": [
                 {
-                    "base_url": "https://eclipse-score.github.io/score/main/",
+                    "base_url": "https://eclipse-score.github.io/score/main",
                     "json_url": "https://eclipse-score.github.io/score/main/needs.json",
                     "version": "0.1",
                     "id_prefix": "score_",
diff --git a/src/BUILD b/src/BUILD
index f9ff91607..ecc9d08b5 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -101,16 +101,35 @@ pkg_tar(
     srcs = [":html_files"],
 )
 
+# 'source_code_linker' needs all targets to be passed to it.
+# This is a convenient gathering of all the 'python internal modules' to avoid writing them individiually
+py_library(
+    name = "docs_as_code_py_modules",
+    srcs = [
+        "@score_docs_as_code//src:plantuml_for_python",
+        "@score_docs_as_code//src/extensions:score_plantuml",
+        "@score_docs_as_code//src/extensions/score_draw_uml_funcs",
+        "@score_docs_as_code//src/extensions/score_header_service",
+        "@score_docs_as_code//src/extensions/score_layout",
+        "@score_docs_as_code//src/extensions/score_metamodel",
+        "@score_docs_as_code//src/extensions/score_source_code_linker",
+        "@score_docs_as_code//src/find_runfiles",
+    ],
+    visibility = ["//visibility:public"],
+)
+
 filegroup(
     name = "score_extension_files",
     srcs = glob(
-        ["*/**"],
+        [
+            "**",
+        ],
         exclude = [
             "**/test/**",
             "**/tests/**",
             "**/__pycache__/**",
         ],
-    ),
+    ) + [":docs_as_code_py_modules"],
     visibility = ["//visibility:public"],
 )
 
diff --git a/src/extensions/score_metamodel/BUILD b/src/extensions/score_metamodel/BUILD
index f180e5553..c2c415df0 100644
--- a/src/extensions/score_metamodel/BUILD
+++ b/src/extensions/score_metamodel/BUILD
@@ -18,7 +18,7 @@ py_library(
     name = "score_metamodel",
     srcs = glob(
         ["**/*.py"],
-    ),
+    ) + ["metamodel.yaml"],
     data = glob(["*.yaml"]),  # Needed to remove 'resolving of symlink' in score_metamodel.__init__
     imports = [
         ".",
diff --git a/src/extensions/score_source_code_linker/__init__.py b/src/extensions/score_source_code_linker/__init__.py
index adff9c5a1..fac87c123 100644
--- a/src/extensions/score_source_code_linker/__init__.py
+++ b/src/extensions/score_source_code_linker/__init__.py
@@ -16,7 +16,7 @@
 
 from sphinx.application import Sphinx
 from sphinx.environment import BuildEnvironment
-from sphinx_needs.data import SphinxNeedsData
+from sphinx_needs.data import NeedsMutable, SphinxNeedsData, NeedsInfoType
 from sphinx_needs.logging import get_logger
 
 from src.extensions.score_source_code_linker.parse_source_files import GITHUB_BASE_URL
@@ -59,6 +59,25 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
     }
 
 
+def find_need(
+    all_needs: NeedsMutable, id: str, prefixes: list[str]
+) -> NeedsInfoType | None:
+    """
+    Checks all possible external 'prefixes' for an ID
+    So that the linker can add the link to the correct NeedsInfoType object.
+    """
+    if id in all_needs:
+        return all_needs[id]
+
+    # Try all possible prefixes
+    for prefix in prefixes:
+        prefixed_id = f"{prefix}{id}"
+        if prefixed_id in all_needs:
+            return all_needs[prefixed_id]
+
+    return None
+
+
 # re-qid: gd_req__req__attr_impl
 def add_source_link(app: Sphinx, env: BuildEnvironment) -> None:
     """
@@ -77,28 +96,25 @@ def add_source_link(app: Sphinx, env: BuildEnvironment) -> None:
     p5 = Path(__file__).parents[5]
 
     if str(p5).endswith("src"):
-        LOGGER.info("DEBUG: WE ARE IN THE IF")
+        LOGGER.debug("DEBUG: WE ARE IN THE IF")
         path = str(p5.parent / Path(app.confdir).name / "score_source_code_parser.json")
     else:
-        LOGGER.info("DEBUG: WE ARE IN THE ELSE")
+        LOGGER.debug("DEBUG: WE ARE IN THE ELSE")
         path = str(p5 / "score_source_code_parser.json")
 
     if app.config.score_source_code_linker_file_overwrite:
         path = app.config.score_source_code_linker_file_overwrite
 
+    # For some reason the prefix 'sphinx_needs internally' is CAPSLOCKED.
+    # So we have to make sure we uppercase the prefixes
+    prefixes = [x["id_prefix"].upper() for x in app.config.needs_external_needs]
     try:
         with open(path) as f:
             gh_json = json.load(f)
         for id, link in gh_json.items():
             id = id.strip()
-            try:
-                # NOTE: Removing & adding the need is important to make sure
-                # the needs gets 're-evaluated'.
-                need = needs_copy[id]  # NeedsInfoType
-                Needs_Data.remove_need(need["id"])
-                need["source_code_link"] = ",".join(link)
-                Needs_Data.add_need(need)
-            except KeyError:
+            need = find_need(needs_copy, id, prefixes)
+            if need is None:
                 # NOTE: manipulating link to remove git-hash,
                 # making the output file location more readable
                 files = [x.replace(GITHUB_BASE_URL, "").split("/", 1)[-1] for x in link]
@@ -107,6 +123,13 @@ def add_source_link(app: Sphinx, env: BuildEnvironment) -> None:
                     + f"Found in file(s): {files}",
                     type="score_source_code_linker",
                 )
+                continue
+
+            # NOTE: Removing & adding the need is important to make sure
+            # the needs gets 're-evaluated'.
+            Needs_Data.remove_need(need["id"])
+            need["source_code_link"] = ",".join(link)
+            Needs_Data.add_need(need)
     except Exception as e:
         LOGGER.warning(
             f"An unexpected error occurred while adding source_code_links to needs."


Reviews:
user: login: dcalavrezo-qorix
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:58:52Z
user: login: AlexanderLanin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T13:03:50Z
user: login: AlexanderLanin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T13:05:48Z
user: login: AlexanderLanin
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-13T13:06:33Z

Review comments:
diff_hunk: @@ -18,7 +18,7 @@ py_library(
     name = "score_metamodel",
     srcs = glob(
         ["**/*.py"],
-    ),
+    ) + ["metamodel.yaml"],
path: src/extensions/score_metamodel/BUILD
user: login: AlexanderLanin
body: why? now it's in src and in data
created_at: 2025-05-13T13:03:50Z
author_association: MEMBER
diff_hunk: @@ -59,6 +59,26 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
     }
 
 
+
+def find_need(
+    all_needs: NeedsMutable, id: str, prefixes: list[str]
+) -> NeedsInfoType | None:
+    """
+    Checks all possible external 'prefixes' for an ID
+    So that the linker can add the link to the correct NeedsInfoType object.
+    """
+    if id in all_needs:
+        return all_needs[id]
+
+    # Try all possible prefixes
+    for prefix in prefixes:
+        prefixed_id = f"{prefix}{id}"
+        if prefixed_id in all_needs:
+            return all_needs[prefixed_id]
+
+    return None
+
+
 # re-qid: gd_req__req__attr_impl
 def add_source_link(app: Sphinx, env: BuildEnvironment) -> None:
path: src/extensions/score_source_code_linker/__init__.py
user: login: AlexanderLanin
body: This is executed only once?!

```suggestion
def add_source_links(app: Sphinx, env: BuildEnvironment) -> None:
```
created_at: 2025-05-13T13:05:47Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-13T12:09:55Z
body: ### License Check Results
🚀 The **license check** job ran with the Bazel command:
```bash
bazel run //src:license-check
```

**Status:** ⚠️ Needs Review

<details>
<summary>Click to expand output</summary>

```
[License Check Output]
Extracting Bazel installation...
Starting local Bazel server and connecting to it...
INFO: Invocation ID: 39b273e2-48d4-41fe-b55a-dea53c5a4606
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Loading: 
Loading: 0 packages loaded
Loading: 0 packages loaded
    currently loading: src
Loading: 0 packages loaded
    currently loading: src
Loading: 0 packages loaded
    currently loading: src
Analyzing: target //src:license-check (1 packages loaded, 0 targets configured)
Analyzing: target //src:license-check (1 packages loaded, 0 targets configured)

Analyzing: target //src:license-check (91 packages loaded, 10 targets configured)

Analyzing: target //src:license-check (94 packages loaded, 10 targets configured)

Analyzing: target //src:license-check (101 packages loaded, 10 targets configured)

Analyzing: target //src:license-check (102 packages loaded, 10 targets configured)

Analyzing: target //src:license-check (102 packages loaded, 10 targets configured)

Analyzing: target //src:license-check (140 packages loaded, 1677 targets configured)

Analyzing: target //src:license-check (148 packages loaded, 2543 targets configured)

Analyzing: target //src:license-check (148 packages loaded, 2543 targets configured)

Analyzing: target //src:license-check (151 packages loaded, 4562 targets configured)

Analyzing: target //src:license-check (152 packages loaded, 4686 targets configured)

Analyzing: target //src:license-check (153 packages loaded, 4806 targets configured)

INFO: Analyzed target //src:license-check (158 packages loaded, 4850 targets configured).
[1 / 1] no actions running
INFO: Found 1 target...
Target //src:license.check.license_check up-to-date:
  bazel-bin/src/license.check.license_check
  bazel-bin/src/license.check.license_check.jar
INFO: Elapsed time: 27.330s, Critical Path: 0.34s
INFO: 13 processes: 4 disk cache hit, 9 internal.
INFO: Build completed successfully, 13 total actions
INFO: Running command line: bazel-bin/src/license.check.license_check src/formatted.txt -review -project automotive.score -repo https://github.com/eclipse-score/docs-as-code -token otyhZ4eaRYK1tKLNNF-Y
[main] INFO Querying Eclipse Foundation for license data for 76 items.
[main] INFO Found 54 items.
[main] INFO Querying ClearlyDefined for license data for 27 items.
[main] INFO Found 27 items.
[main] INFO License information could not be automatically verified for the following content:
[main] INFO 
[main] INFO pypi/pypi/-/babel/2.17.0
[main] INFO pypi/pypi/-/fonttools/4.57.0
[main] INFO pypi/pypi/-/pillow/11.2.1
[main] INFO pypi/pypi/-/roman-numerals-py/3.1.0
[main] INFO pypi/pypi/-/sphinx/8.2.3
[main] INFO 
[main] INFO This content is either not correctly mapped by the system, or requires review.
[main] INFO A review is required for pypi/pypi/-/pillow/11.2.1.
[main] INFO A review request already exists https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/21057 .
[main] INFO A review is required for pypi/pypi/-/roman-numerals-py/3.1.0.
[main] INFO A review request already exists https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/21058 .
[main] INFO A review is required for pypi/pypi/-/babel/2.17.0.
[main] INFO A review request already exists https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/21059 .
[main] INFO A review is required for pypi/pypi/-/sphinx/8.2.3.
[main] INFO A review request already exists https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/21061 .
[main] INFO A review is required for pypi/pypi/-/fonttools/4.57.0.
[main] INFO A review request already exists https://gitlab.eclipse.org/eclipsefdn/emo-team/iplab/-/issues/21064 .
```

</details>
author_association: NONE
user: login: AlexanderLanin
created_at: 2025-05-13T13:07:07Z
body: Please reformat extensions/score_source_code_linker/__init__.py
author_association: MEMBER
user: login: MaximilianSoerenPollak
created_at: 2025-05-13T13:13:34Z
body: > Please reformat extensions/score_source_code_linker/**init**.py

So weird, could have sworn I did the formatting locally before pushing. 
Fixed it now :)

author_association: CONTRIBUTOR