Repository navigation
arch/stm32: unify timer drivers #3791
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: MemBrowse Memory Report | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - master | |
| - "releases/*" | |
| # pull-requests read: needed to re-read edited Depends-On declarations on re-runs. | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| # Per-PR group so superseded PR pushes cancel; per-SHA on push so master | |
| # commits never share a group. A shared refs/heads/master group lets a burst | |
| # of pushes evict each other while pending (GitHub keeps only one pending run | |
| # per group), leaving a commit with no report and breaking the MemBrowse | |
| # parent chain for every commit that bases on it. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Detect whether any source files (i.e. non-doc/CODEOWNERS) changed. | |
| # When only docs/CODEOWNERS change we skip the build and post an "identical" | |
| # MemBrowse report instead. | |
| changes-filter: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| source: ${{ steps.filter.outputs.source }} | |
| steps: | |
| # Shallow checkout; the base commit needed for the diff is fetched | |
| # explicitly below instead of cloning the repo's full history. | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 2 | |
| # Detect whether any non-doc/CODEOWNERS source files changed. | |
| # Implemented with plain git instead of a third-party paths-filter | |
| # action, since Apache's GitHub Actions allowlist forbids actions that | |
| # aren't GitHub-created or explicitly permitted. | |
| - id: filter | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # On the very first push to a branch, github.event.before is all-zeros. | |
| if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then | |
| echo "source=true" >> "$GITHUB_OUTPUT" | |
| echo "No usable base SHA; treating as source change." | |
| exit 0 | |
| fi | |
| # Diff from the merge-base (fork point) so only this PR/push's own | |
| # commits count. For pull_request events BASE_SHA is the current base | |
| # branch tip, which drifts ahead of the fork point as the base branch | |
| # advances; diffing from the merge-base isolates the PR's real changes | |
| # (e.g. keeps a docs-only PR classified as docs-only). | |
| # | |
| # GitHub's compare API uses 3-dot semantics, so .merge_base_commit.sha | |
| # is the fork point. gh is pre-installed on the runner. | |
| MERGE_BASE=$(gh api \ | |
| "repos/$GITHUB_REPOSITORY/compare/$BASE_SHA...$HEAD_SHA" \ | |
| --jq '.merge_base_commit.sha' 2>/dev/null || true) | |
| if [ -z "$MERGE_BASE" ]; then | |
| # API unavailable/unexpected: fail safe toward building. | |
| echo "source=true" >> "$GITHUB_OUTPUT" | |
| echo "Could not resolve merge-base via API; treating as source change." | |
| exit 0 | |
| fi | |
| echo "Merge-base: $MERGE_BASE" | |
| # The shallow checkout has HEAD but may lack the merge-base commit; | |
| # fetch just that one commit so the local diff can read its tree. | |
| git cat-file -e "$MERGE_BASE^{commit}" 2>/dev/null \ | |
| || git fetch --depth=1 origin "$MERGE_BASE" 2>/dev/null || true | |
| # MERGE_BASE is an ancestor of HEAD, so this diff yields the PR/push's | |
| # own changes. | |
| if ! changed=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA" 2>/dev/null); then | |
| echo "source=true" >> "$GITHUB_OUTPUT" | |
| echo "git diff against merge-base failed; treating as source change." | |
| exit 0 | |
| fi | |
| echo "Changed files:" | |
| echo "$changed" | |
| # Drop paths that should NOT count as source changes; if anything | |
| # survives, real source changed. | |
| source=$(echo "$changed" | grep -vE \ | |
| -e '^AUTHORS$' \ | |
| -e '^CONTRIBUTING\.md$' \ | |
| -e '(^|/)CODEOWNERS$' \ | |
| -e '^Documentation/' \ | |
| -e '^tools/ci/docker/linux/' \ | |
| -e '^tools/codeowners/[^/]+$' \ | |
| || true) | |
| if [ -n "$source" ]; then | |
| echo "source=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "source=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| load-targets: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - id: set-matrix | |
| run: echo "matrix=$(jq -c '.' .github/membrowse-targets.json)" >> $GITHUB_OUTPUT | |
| # Post an "identical" MemBrowse report when only docs/CODEOWNERS changed. | |
| # Only runs on push events (master/releases/tags) to keep the baseline | |
| # complete; PR events don't produce identical markers. | |
| identical: | |
| needs: [changes-filter, load-targets] | |
| if: needs.changes-filter.outputs.source == 'false' && github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.load-targets.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 2 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.11' | |
| - name: Install membrowse | |
| run: pip install --no-cache-dir membrowse | |
| - name: Upload identical - ${{ matrix.target_name }} | |
| env: | |
| MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }} | |
| MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }} | |
| TARGET_NAME: ${{ matrix.target_name }} | |
| # Base on the previous branch tip, not the (often unreported mid-PR) | |
| # git parent. | |
| BEFORE_SHA: ${{ github.event.before }} | |
| run: | | |
| ARGS=(--upload --github | |
| --target-name "$TARGET_NAME" | |
| --api-key "$MEMBROWSE_API_KEY" | |
| --identical) | |
| if [ -n "$MEMBROWSE_API_URL" ]; then | |
| ARGS+=(--api-url "$MEMBROWSE_API_URL") | |
| fi | |
| # Override parent with previous tip; skip on branch creation (0s). | |
| if [ -n "$BEFORE_SHA" ] && [ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then | |
| ARGS+=(--base-sha "$BEFORE_SHA") | |
| fi | |
| membrowse report "${ARGS[@]}" | |
| # Build target with debug symbols + linker map, then upload MemBrowse report. | |
| # Uses the NuttX CI Docker image so toolchains, kconfig-frontends, etc. are | |
| # already installed. | |
| analyze: | |
| needs: [changes-filter, load-targets] | |
| if: needs.changes-filter.outputs.source == 'true' | |
| runs-on: ubuntu-latest | |
| env: | |
| DOCKER_BUILDKIT: 1 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.load-targets.outputs.matrix) }} | |
| steps: | |
| - name: Checkout nuttx repo | |
| uses: actions/checkout@v7 | |
| with: | |
| path: sources/nuttx | |
| fetch-depth: 2 | |
| - name: Checkout nuttx-apps repo | |
| uses: actions/checkout@v7 | |
| with: | |
| repository: apache/nuttx-apps | |
| path: sources/apps | |
| fetch-depth: 1 | |
| # Free space before pulling the large NuttX CI image; ubuntu-latest ships | |
| # only ~14 GB free on / and the image otherwise fails to unpack with | |
| # "no space left on device". Mirrors the cleanup in .github/workflows/build.yml. | |
| - name: Show Disk Space | |
| run: df -h | |
| - name: Free Disk Space (Ubuntu) | |
| run: | | |
| sudo rm -rf /usr/local/lib/android | |
| - name: After CLEAN-UP Disk Space | |
| run: df -h | |
| # Apply declared nuttx/apps dependencies before measuring, matching | |
| # build.yml's checkout mapping and cherry-pick order. API or parser | |
| # failures and parsed dependencies that cannot be applied are fatal, so a | |
| # report is never taken from the wrong source set. Keep this after the | |
| # disk cleanup: applying a dependency deepens a checkout. | |
| - name: Apply depends-on PRs | |
| if: ${{ github.event_name == 'pull_request' && github.base_ref == 'master' }} | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -uo pipefail | |
| # Re-read the current body so a re-run picks up an edited Depends-On | |
| # line. Do not fall back to the event payload: unlike build.yml, which | |
| # resolves this once for every target, each matrix leg resolves | |
| # independently and could otherwise use a different declaration. | |
| if ! PR_BODY="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"; then | |
| echo "::error::Could not read the PR description." | |
| exit 1 | |
| fi | |
| # A missing parser, a crash, or an unknown status all leave the | |
| # declaration unevaluated. | |
| PARSER=sources/nuttx/.github/scripts/depends_on.py | |
| if [ ! -f "$PARSER" ]; then | |
| echo "::error::${PARSER} not found." | |
| exit 1 | |
| fi | |
| STATE="${RUNNER_TEMP:-/tmp}/depends-on-state.txt" | |
| if ! python3 "$PARSER" --print-state > "$STATE"; then | |
| echo "::error::Could not parse the depends-on declarations." | |
| exit 1 | |
| fi | |
| # --print-state omits warnings. Run --github-output again with its | |
| # outputs and report discarded so dropped entries remain visible as | |
| # workflow annotations. | |
| if ! WARNINGS="$(GITHUB_OUTPUT=/dev/null REPORT_PATH= python3 "$PARSER" --github-output)"; then | |
| echo "::error::Could not re-read the depends-on warnings." | |
| exit 1 | |
| fi | |
| printf '%s\n' "$WARNINGS" | grep '^::warning::' || true | |
| STATUS=$(head -n 1 "$STATE") | |
| case "$STATUS" in | |
| ok) ;; | |
| none) | |
| echo "No Depends-On declaration; building against the default sources." | |
| exit 0 ;; | |
| invalid) | |
| # The parser warning forwarded above already explains this. | |
| echo "No valid dependency parsed; building against the default sources." | |
| exit 0 ;; | |
| *) | |
| echo "::error::Unexpected depends-on parser status: ${STATUS}" | |
| exit 1 ;; | |
| esac | |
| git config --global user.email "actions@github.com" | |
| git config --global user.name "github-actions" | |
| # ok must carry at least one reference; an unreadable or empty list | |
| # would skip the loop and report the default sources as though the | |
| # declaration had been applied. Keep the loop in this shell, not a | |
| # pipeline, so a dependency failure exits the step. | |
| if ! tail -n +2 "$STATE" > "${STATE}.refs" || [ ! -s "${STATE}.refs" ]; then | |
| echo "::error::Could not read the parsed dependency list." | |
| exit 1 | |
| fi | |
| # The report is keyed by the pull request head SHA, which names | |
| # neither checkout: nuttx is the pull request merged into its base, | |
| # and apps is an unpinned default branch the event never mentions. | |
| # Record both in the summary, before anything is applied, so the | |
| # source set is visible where the report is read. | |
| NUTTX_CHECKOUT_SHA=$(git -C sources/nuttx rev-parse HEAD) | |
| APPS_CHECKOUT_SHA=$(git -C sources/apps rev-parse HEAD) | |
| { | |
| echo "### Depends-On source set" | |
| echo | |
| echo "Measured under this pull request's head SHA, together with:" | |
| echo "- \`apache/nuttx\` checkout @ \`${NUTTX_CHECKOUT_SHA}\`" | |
| echo "- \`apache/nuttx-apps\` checkout @ \`${APPS_CHECKOUT_SHA}\`" | |
| } >> "${GITHUB_STEP_SUMMARY:-/dev/null}" | |
| while read -r DEP; do | |
| [ -n "$DEP" ] || continue | |
| DEP_REPO=${DEP%/pull/*} | |
| DEP_NUM=${DEP##*/} | |
| # Keep the repository mapping aligned with build.yml. | |
| case "$DEP_REPO" in | |
| "apache/nuttx") REPO_PATH=sources/nuttx ;; | |
| "apache/nuttx-apps") REPO_PATH=sources/apps ;; | |
| *) | |
| echo "::error::Unsupported dependency repository: ${DEP_REPO}" | |
| exit 1 ;; | |
| esac | |
| echo "Applying dependency ${DEP}" | |
| # Deepening is best effort; the common-base and rev-list checks | |
| # below still fail closed. Say it happened, so a missing common | |
| # base is not mistaken for an unrelated dependency. | |
| if [ -f "${REPO_PATH}/.git/shallow" ] \ | |
| && ! git -C "$REPO_PATH" fetch --unshallow origin; then | |
| echo "::warning::Could not deepen ${REPO_PATH}; a missing common base below may follow from that rather than from ${DEP}." | |
| fi | |
| if ! git -C "$REPO_PATH" fetch origin "pull/${DEP_NUM}/head:dep-${DEP_NUM}"; then | |
| echo "::error::Could not fetch ${DEP} (the PR may not exist)." | |
| exit 1 | |
| fi | |
| # Reject unrelated histories before computing HEAD..dep, which | |
| # would otherwise list every dependency commit. | |
| if [ -z "$(git -C "$REPO_PATH" merge-base "dep-${DEP_NUM}" HEAD || true)" ]; then | |
| echo "::error::Could not find a common base with ${DEP}." | |
| exit 1 | |
| fi | |
| if ! COMMITS=$(git -C "$REPO_PATH" rev-list --reverse "HEAD..dep-${DEP_NUM}"); then | |
| echo "::error::Could not list the commits of ${DEP}." | |
| exit 1 | |
| fi | |
| DEP_SHA=$(git -C "$REPO_PATH" rev-parse "dep-${DEP_NUM}") | |
| if [ -z "$COMMITS" ]; then | |
| echo "Dependency ${DEP} is already included." | |
| echo "- \`${DEP}\` @ \`${DEP_SHA}\` (already in the checkout)" \ | |
| >> "${GITHUB_STEP_SUMMARY:-/dev/null}" | |
| continue | |
| fi | |
| # shellcheck disable=SC2086 | |
| if ! git -C "$REPO_PATH" cherry-pick $COMMITS; then | |
| echo "::error::Could not cherry-pick ${DEP}." | |
| echo "::error::If your pull request contains merge commits, rebase instead of merging." | |
| git -C "$REPO_PATH" cherry-pick --abort || true | |
| exit 1 | |
| fi | |
| echo "Applied ${DEP} @ ${DEP_SHA}" | |
| echo "- \`${DEP}\` @ \`${DEP_SHA}\`" \ | |
| >> "${GITHUB_STEP_SUMMARY:-/dev/null}" | |
| done < "${STATE}.refs" | |
| - name: Docker Login | |
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Docker Pull | |
| run: docker pull ghcr.io/apache/nuttx/apache-nuttx-ci-linux | |
| - name: Build ${{ matrix.target_name }} | |
| uses: ./sources/nuttx/.github/actions/ci-container | |
| with: | |
| run: | | |
| git config --global --add safe.directory /github/workspace/sources/nuttx | |
| git config --global --add safe.directory /github/workspace/sources/apps | |
| cd /github/workspace/sources/nuttx | |
| ./tools/configure.sh -l ${{ matrix.board_config }} | |
| echo CONFIG_DEBUG_SYMBOLS=y >> .config | |
| echo CONFIG_DEBUG_LINK_MAP=y >> .config | |
| if [ -n "${{ matrix.config_overrides }}" ]; then | |
| kconfig-tweak ${{ matrix.config_overrides }} | |
| fi | |
| make olddefconfig | |
| # Preserve preprocessed linker scripts (.ld.tmp) so MemBrowse can | |
| # read them. Arch Makefiles delete these immediately after linking. | |
| find arch -name Makefile -exec sed -i '/DELFILE, $(addsuffix .tmp,$(ARCHSCRIPT))/d' {} + | |
| make -j$(nproc) | |
| # MemBrowse action runs from $GITHUB_WORKSPACE and discovers git via CWD, | |
| # but nuttx is checked out to sources/nuttx/ (so apps can sit beside it). | |
| # Expose the repo's .git at the workspace root so git metadata resolves. | |
| - name: Expose nuttx .git to workspace root | |
| run: ln -sfn sources/nuttx/.git .git | |
| - name: Prefix linker script paths | |
| id: ld | |
| run: | | |
| prefixed="" | |
| for p in ${{ matrix.ld }}; do | |
| prefixed="$prefixed sources/nuttx/$p" | |
| done | |
| paths=$(echo $prefixed | xargs) | |
| echo "paths=$paths" >> "$GITHUB_OUTPUT" | |
| echo "Resolved ld paths: [$paths]" | |
| for p in $paths; do | |
| if [ -e "$p" ]; then | |
| echo " OK $p ($(stat -c '%s bytes, owner=%U:%G' "$p" 2>/dev/null))" | |
| else | |
| echo " MISS $p" | |
| echo " ls dir:" | |
| ls -la "$(dirname "$p")" 2>&1 | head -30 | |
| fi | |
| done | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: '3.11' | |
| - name: Install membrowse | |
| run: pip install --no-cache-dir membrowse | |
| - name: MemBrowse analysis - ${{ matrix.target_name }} | |
| env: | |
| MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }} | |
| MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }} | |
| TARGET_NAME: ${{ matrix.target_name }} | |
| ELF: sources/nuttx/${{ matrix.elf }} | |
| LD_PATHS: ${{ steps.ld.outputs.paths }} | |
| MAP_FILE: ${{ matrix.map_file != '' && format('sources/nuttx/{0}', matrix.map_file) || '' }} | |
| LINKER_VARS: ${{ matrix.linker_vars }} | |
| # Push only: base on the previous branch tip, not the (often | |
| # unreported mid-PR) git parent. Empty on PRs (--github bases on the | |
| # PR target tip). | |
| BEFORE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }} | |
| run: | | |
| set -o pipefail | |
| ARGS=("$ELF" "$LD_PATHS" | |
| --upload --github | |
| --target-name "$TARGET_NAME" | |
| --api-key "$MEMBROWSE_API_KEY") | |
| if [ -n "$MEMBROWSE_API_URL" ]; then | |
| ARGS+=(--api-url "$MEMBROWSE_API_URL") | |
| fi | |
| # Override parent with previous tip; skip on branch creation (0s). | |
| if [ -n "$BEFORE_SHA" ] && [ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then | |
| ARGS+=(--base-sha "$BEFORE_SHA") | |
| fi | |
| if [ -n "$MAP_FILE" ]; then | |
| ARGS+=(--map-file "$MAP_FILE") | |
| fi | |
| if [ -n "$LINKER_VARS" ]; then | |
| set -f | |
| for var in $LINKER_VARS; do | |
| ARGS+=(--def "$var") | |
| done | |
| set +f | |
| fi | |
| membrowse --verbose INFO report "${ARGS[@]}" |