Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/build-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,17 @@ jobs:
ccache --version
ccache --show-config

# Graph-affecting build options (BUILD_VIZ / BUILD_PLUGIN_OAK_CAMERA /
# ENABLE_CLOUDXR_BUNDLE_CHECK / plugins / examples / tests / python bindings) live in the
# `ci-linux` preset in CMakePresets.json, the single source shared with the
# cmake-target-layers diagram workflow. Only the per-matrix/tooling vars are overlaid here.
- name: Configure CMake
run: |
cmake -B build \
cmake --preset ci-linux \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DISAAC_TELEOP_PYTHON_VERSION=${{ matrix.python_version }} \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DBUILD_PLUGIN_OAK_CAMERA=ON \
-DBUILD_VIZ=ON \
-DENABLE_CLOUDXR_BUNDLE_CHECK=ON \
-DBUNDLE_ROBOTIC_GROUNDING=${{ steps.setup-v2d-src.outputs.bundled || 'false' }}

- name: Build
Expand Down
122 changes: 122 additions & 0 deletions .github/workflows/cmake-target-layers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Verify that cmake/cmake-target-layers.md matches the live CMake target graph.
#
# The graph is read straight from CMake: we configure with the SAME `ci-linux` preset that the
# primary Linux build uses (CMakePresets.json is the single source of the graph-affecting build
# options), ask CMake to emit its own dependency graph (`cmake --graphviz`), and regenerate the
# layered diagram. If the committed copy drifts from what CMake reports, the job fails — and the
# freshly-generated diagram is always uploaded as the `cmake-target-layers` artifact, so the fix
# is to download it and commit it in place of the stale file (no local full build required).
#
# Only graph-NEUTRAL build gates are relaxed here (ENABLE_CLOUDXR_BUNDLE_CHECK and
# ENABLE_CLANG_FORMAT_CHECK gate python/utility/custom targets that `cmake --graphviz` excludes),
# which keeps this job free of the CloudXR SDK / NGC secret so it also runs on fork PRs. Every
# build-affecting flag still comes from the shared preset, so the diagram cannot diverge from the
# real build's target graph.

name: Verify CMake target layers

on:
push:
branches: [ main, 'release/*.*.x' ]
paths:
- '**/CMakeLists.txt'
- '**/*.cmake'
- 'CMakePresets.json'
- 'scripts/cmake_target_layers.py'
- 'cmake/cmake-target-layers.md'
- '.github/workflows/cmake-target-layers.yaml'
pull_request:
paths:
- '**/CMakeLists.txt'
- '**/*.cmake'
- 'CMakePresets.json'
- 'scripts/cmake_target_layers.py'
- 'cmake/cmake-target-layers.md'
- '.github/workflows/cmake-target-layers.yaml'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
verify-target-layers:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the workflow file in question
FILE=".github/workflows/cmake-target-layers.yaml"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "== File: $FILE =="
# Print around the referenced lines (53, 74, 99) with some context
nl -ba "$FILE" | sed -n '35,120p'

Repository: NVIDIA/IsaacTeleop

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/cmake-target-layers.yaml"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "== File: $FILE =="
nl -ba "$FILE" | sed -n '35,120p'

Repository: NVIDIA/IsaacTeleop

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/cmake-target-layers.yaml"
[ -f "$FILE" ] || { echo "Missing file: $FILE"; exit 1; }

echo "== File: $FILE =="
awk -v start=30 -v end=120 '{if(NR>=start && NR<=end) printf "%5d\t%s\n", NR, $0}' "$FILE"

Repository: NVIDIA/IsaacTeleop

Length of output: 3951


Pin GitHub Actions uses to immutable commit SHAs (supply-chain hardening).

In .github/workflows/cmake-target-layers.yaml, these uses: use mutable tags—pin each to a full commit SHA instead of @v*:

  • actions/checkout@v6 (line 53)
  • actions/cache@v5 (line 74)
  • actions/upload-artifact@v6 (line 99)
🧰 Tools
🪛 zizmor (1.25.2)

[error] 53-53: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cmake-target-layers.yaml at line 53, Replace mutable
GitHub Actions tags with immutable commit SHAs for supply-chain hardening:
locate the three `uses:` entries `actions/checkout@v6`, `actions/cache@v5`, and
`actions/upload-artifact@v6` in the workflow and replace each tag with the
corresponding full commit SHA for the action repo (e.g.,
`actions/checkout@<full-sha>`). Ensure you fetch the correct commit SHA from the
respective official action repository (tags/releases page) and update the YAML
entries so they point to those SHAs instead of `@v*`, then verify the workflow
runs successfully.

Source: Linters/SAST tools

with:
fetch-depth: 0 # Full history for accurate git describe during configure

- name: Install uv
uses: ./.github/actions/setup-uv

# viz_core links libcudart, so find_package(CUDAToolkit) must resolve at configure time
# for BUILD_VIZ=ON (set by the ci-linux preset).
- name: Install CUDA toolkit
uses: ./.github/actions/setup-cuda

- name: Install Apt dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libx11-dev libvulkan-dev glslang-tools \
libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev libxkbcommon-dev \
libwayland-dev wayland-protocols

# BUILD_PLUGIN_OAK_CAMERA=ON (ci-linux preset) pulls DepthAI via Hunter at configure time.
- name: Cache Hunter packages
uses: actions/cache@v5
with:
path: ~/.hunter
key: hunter-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cmake/SetupHunter.cmake') }}
restore-keys: |
hunter-${{ runner.os }}-${{ runner.arch }}-

# Configure only (no build) with the shared preset; emit CMake's own dependency graph.
# The two -D overlays are graph-neutral (see header) and drop the CloudXR/clang-format needs.
- name: Configure CMake and emit dependency graph
run: |
cmake --preset ci-linux \
-DENABLE_CLOUDXR_BUNDLE_CHECK=OFF \
-DENABLE_CLANG_FORMAT_CHECK=OFF \
--graphviz=build/cmake-target-graph.dot

- name: Regenerate diagram (artifact)
run: |
python3 scripts/cmake_target_layers.py \
--dot build/cmake-target-graph.dot \
--out artifact/cmake-target-layers.md

# Upload regardless of the check result so a stale committed copy can be replaced by this file.
- name: Upload diagram artifact
if: always()
uses: actions/upload-artifact@v6
with:
name: cmake-target-layers
path: artifact/cmake-target-layers.md
if-no-files-found: error

- name: Verify committed diagram is up to date
id: verify-diagram
run: |
python3 scripts/cmake_target_layers.py \
--dot build/cmake-target-graph.dot \
--check

- name: Hint on stale diagram
if: failure() && steps.verify-diagram.conclusion == 'failure'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
if [ -n "$PR_URL" ]; then
HINT="Comment \`/update-cmake-target-layers\` on the PR to auto-commit the refreshed diagram."
else
HINT="Download the \`cmake-target-layers\` artifact from this run and commit it in place of \`cmake/cmake-target-layers.md\`."
fi
echo "::error title=cmake-target-layers.md is stale::$HINT"
186 changes: 186 additions & 0 deletions .github/workflows/update-cmake-target-layers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Slash-command handler: /update-cmake-target-layers
#
# When a maintainer with write access comments `/update-cmake-target-layers` on a PR,
# this workflow downloads the cmake-target-layers artifact that the "Verify CMake target
# layers" CI job already generated on its most recent successful run and commits it to
# the PR branch via the GitHub Contents API. No checkout of PR code, no cmake re-run.
#
# Only same-repo PRs are supported; fork PRs cannot be pushed to from this context.
#
# The workflow file is always loaded from the default branch, so a PR cannot override
# it to gain elevated write-access.

name: Update CMake target layers

on:
issue_comment:
types: [created]

jobs:
update:
name: Commit refreshed cmake-target-layers.md
if: >-
github.event_name == 'issue_comment'
&& github.event.issue.pull_request != null
&& (github.event.comment.body == '/update-cmake-target-layers'
|| startsWith(github.event.comment.body, '/update-cmake-target-layers '))
runs-on: ubuntu-latest
concurrency:
group: update-cmake-target-layers-pr-${{ github.event.issue.number }}
cancel-in-progress: true
permissions:
actions: read # download artifact from the verify run
contents: write # push to the PR branch
pull-requests: write # comment on the PR
issues: write # react to the triggering comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
COMMENT_ID: ${{ github.event.comment.id }}
PR_NUMBER: ${{ github.event.issue.number }}

steps:
- name: Verify commenter has write access
env:
USER: ${{ github.event.comment.user.login }}
run: |
PERM=$(gh api "repos/$REPO/collaborators/$USER/permission" --jq '.permission')
case "$PERM" in
admin|maintain|write)
echo "$USER has $PERM access — proceeding."
;;
*)
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='-1' >/dev/null
echo "::error::User $USER has '$PERM' access; /update-cmake-target-layers requires write or higher." >&2
exit 1
;;
esac

- name: Acknowledge command
run: |
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='eyes' >/dev/null

- name: Get PR info
id: pr
run: |
read -r HEAD_SHA HEAD_REF HEAD_REPO < <(
gh api "repos/$REPO/pulls/$PR_NUMBER" \
--jq '[.head.sha, .head.ref, .head.repo.full_name] | @tsv'
)

echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT"
Comment on lines +68 to +77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PR head TOCTOU can commit a stale diagram onto a newer branch tip.

The workflow resolves a verify artifact for one HEAD_SHA but later commits to HEAD_REF without revalidating that the PR head is still that SHA. If new commits land mid-run, this can write an outdated artifact onto the latest branch state.

Suggested fix
       - name: Commit refreshed diagram via API
         id: commit
         env:
           COMMENTER: ${{ github.event.comment.user.login }}
           COMMENTER_ID: ${{ github.event.comment.user.id }}
           HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
           HEAD_REF: ${{ steps.pr.outputs.head_ref }}
           RUN_ID: ${{ steps.find-run.outputs.run_id }}
         run: |
+          CURRENT_HEAD_SHA=$(gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.head.sha')
+          if [ "$CURRENT_HEAD_SHA" != "$HEAD_SHA" ]; then
+            echo "::error::PR head moved from ${HEAD_SHA:0:8} to ${CURRENT_HEAD_SHA:0:8}; rerun /update-cmake-target-layers."
+            exit 1
+          fi
+
           # Get blob SHA of the currently-committed file (required to update it via API).
           FILE_META=$(gh api "repos/$REPO/contents/cmake/cmake-target-layers.md?ref=$HEAD_REF" \
             2>/dev/null || echo '{}')

Also applies to: 91-113, 126-164

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/update-cmake-target-layers.yaml around lines 68 - 77, The
workflow currently fetches and stores head_sha/head_ref in the "Get PR info"
step (using gh api and writing to GITHUB_OUTPUT) but later commits to head_ref
without revalidating that the PR head still matches head_sha, allowing a TOCTOU
race; fix by re-querying the PR head SHA via gh api just before making any
commit/push (the same logic used in the "Get PR info" step), compare the fresh
SHA to the stored head_sha output, and if they differ abort (or update the
artifact generation to target the new SHA) so you never write a stale artifact
to the latest branch; apply this check in the later commit steps that perform
the push/commit (the steps that consume head_ref/head_sha).


if [ "$HEAD_REPO" != "$REPO" ]; then
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='-1' >/dev/null
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"❌ \`/update-cmake-target-layers\` only works for same-repository PRs. Fork PRs cannot be pushed to from this context. Download the \`cmake-target-layers\` artifact from the failing CI run and commit it manually."
echo "::error::Fork PR — cannot push to $HEAD_REPO from $REPO context." >&2
exit 1
fi

# Reuse the artifact the verify job already uploaded on its successful run.
# Only success runs are eligible: a failure run may have crashed before the
# Regenerate step and therefore has no artifact to download.
- name: Find verify run for PR head SHA
id: find-run
env:
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
run: |
RUN_ID=$(gh run list --repo "$REPO" \
--workflow=cmake-target-layers.yaml \
--commit "$HEAD_SHA" \
--limit 10 \
--json databaseId,conclusion \
--jq '[.[] | select(.conclusion == "success")] | .[0].databaseId // ""')

if [ -z "$RUN_ID" ]; then
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='-1' >/dev/null
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"❌ No successful **Verify CMake target layers** run found for commit \`${HEAD_SHA:0:8}\`. Wait for the CI job to pass, then re-comment \`/update-cmake-target-layers\`."
echo "::error::No successful cmake-target-layers.yaml run for $HEAD_SHA" >&2
exit 1
fi

echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
echo "Reusing artifact from cmake-target-layers.yaml run #$RUN_ID for SHA \`${HEAD_SHA:0:8}\`" \
>> "$GITHUB_STEP_SUMMARY"

- name: Download cmake-target-layers artifact
uses: actions/download-artifact@v7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pin actions/download-artifact to an immutable commit SHA (Line 117)

This workflow uses a mutable major tag (uses: actions/download-artifact@v7) in a job with write permissions, weakening supply-chain guarantees. Pin the action to a full commit SHA instead of @v7.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 117-117: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/update-cmake-target-layers.yaml at line 117, Replace the
mutable tag uses: actions/download-artifact@v7 with a pinned immutable commit
SHA by locating the uses line (uses: actions/download-artifact@v7) in the
workflow and updating it to uses: actions/download-artifact@<full-commit-sha>;
fetch the latest commit SHA from the actions/download-artifact repository (or
the release you intend to track), verify compatibility, and commit the change so
the workflow uses the fixed SHA instead of the `@v7` tag.

with:
name: cmake-target-layers
path: ./artifact
run-id: ${{ steps.find-run.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

# Commit via the Contents API — no checkout of PR code required.
# This avoids the "untrusted checkout in a privileged context" security pattern.
- name: Commit refreshed diagram via API
id: commit
env:
COMMENTER: ${{ github.event.comment.user.login }}
COMMENTER_ID: ${{ github.event.comment.user.id }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
HEAD_REF: ${{ steps.pr.outputs.head_ref }}
RUN_ID: ${{ steps.find-run.outputs.run_id }}
run: |
# Get blob SHA of the currently-committed file (required to update it via API).
FILE_META=$(gh api "repos/$REPO/contents/cmake/cmake-target-layers.md?ref=$HEAD_REF" \
2>/dev/null || echo '{}')
CURRENT_SHA=$(echo "$FILE_META" | jq -r '.sha // ""')

# Compare base64 strings directly — API returns base64 with line breaks, strip them.
ARTIFACT_B64=$(base64 -w 0 ./artifact/cmake-target-layers.md)
CURRENT_B64=$(echo "$FILE_META" | jq -r '.content // ""' | tr -d '\n')
if [ "$ARTIFACT_B64" = "$CURRENT_B64" ]; then
echo "already_current=true" >> "$GITHUB_OUTPUT"
exit 0
fi

COMMIT_MSG=$(printf \
'docs(cmake): regenerate cmake-target-layers.md\n\nAuto-committed by /update-cmake-target-layers (PR #%s).\nSource: cmake-target-layers.yaml run #%s, head %s.' \
"$PR_NUMBER" "$RUN_ID" "$HEAD_SHA")

jq -n \
--arg message "$COMMIT_MSG" \
--arg content "$ARTIFACT_B64" \
--arg sha "$CURRENT_SHA" \
--arg branch "$HEAD_REF" \
--arg name "$COMMENTER" \
--arg email "${COMMENTER_ID}+${COMMENTER}@users.noreply.github.com" \
'{message: $message, content: $content, branch: $branch,
committer: {name: $name, email: $email},
author: {name: $name, email: $email}}
| if $sha != "" then . + {sha: $sha} else . end' \
| gh api -X PUT "repos/$REPO/contents/cmake/cmake-target-layers.md" --input -

- name: Confirm update
if: always()
env:
ALREADY_CURRENT: ${{ steps.commit.outputs.already_current }}
COMMIT_OUTCOME: ${{ steps.commit.outcome }}
run: |
if [ "$COMMIT_OUTCOME" = "success" ]; then
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='+1' >/dev/null
if [ "$ALREADY_CURRENT" = "true" ]; then
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"✅ \`cmake/cmake-target-layers.md\` is already up to date with the latest CI run — nothing to commit."
else
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"✅ \`cmake/cmake-target-layers.md\` has been refreshed and committed to this branch."
fi
else
gh api -X POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
-f content='-1' >/dev/null
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"❌ Failed to commit \`cmake/cmake-target-layers.md\` — check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details."
fi
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ only point here — edit the rules in the doc, not the shims.
clang-format --dry-run --Werror $(git diff --name-only main -- '*.cpp' '*.hpp' '*.h' '*.cc')
```

- If a formatter hook rewrites files (for example `ruff format`), keep the mechanical rewrite and rerun the full pre-commit command until it passes.
- If a hook failure shows **missing or non-obvious repo policy** (not a one-off typo), you **must** add a **short** reminder under **Mandatory learning loop** rules to the right `AGENTS.md` or adjacent **`//` comments** so the next run does not repeat it—unless it is already documented.

## Mandatory learning loop (AGENTS.md and comments)
Expand Down
25 changes: 25 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 21,
"patch": 0
},
"configurePresets": [
{
"name": "ci-linux",
"displayName": "Linux CI build (full)",
"description": "Canonical Linux CI configuration. Single source of truth for the graph-affecting build options, shared with the cmake-target-layers diagram workflow.",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"BUILD_VIZ": "ON",
"BUILD_PLUGINS": "ON",
"BUILD_PLUGIN_OAK_CAMERA": "ON",
"BUILD_EXAMPLES": "ON",
"BUILD_TESTING": "ON",
"BUILD_PYTHON_BINDINGS": "ON",
"ENABLE_CLOUDXR_BUNDLE_CHECK": "ON"
}
}
]
}
2 changes: 2 additions & 0 deletions CMakePresets.json.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
5 changes: 5 additions & 0 deletions cmake/cmake-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ as humans. Apply them both when **authoring** code/build files and when
> and the root `AGENTS.md`) point here — edit the rules **here**, not in those
> shims.

> **See also:** [`cmake-target-layers.md`](cmake-target-layers.md) — an auto-generated,
> layered view of the *actual* CMake target dependency graph (direct edges only, sorted into
> topological layers), regenerated from live CMake and CI-verified by the *Verify CMake target
> layers* workflow.

## The six rules (non-negotiable)

1. **One CMake target per leaf directory.** A directory that defines a target
Expand Down
Loading
Loading