diff --git a/.github/membrowse-targets.json b/.github/membrowse-targets.json new file mode 100644 index 0000000000000..f0a227f4dbb8d --- /dev/null +++ b/.github/membrowse-targets.json @@ -0,0 +1,83 @@ +[ + { + "target_name": "stm32-nucleo-f103rb", + "board_config": "nucleo-f103rb:nsh", + "elf": "nuttx", + "ld": "boards/arm/stm32/nucleo-f103rb/scripts/ld.script", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "arduino-mega2560", + "board_config": "arduino-mega2560:nsh", + "elf": "nuttx.elf", + "ld": "boards/avr/atmega/arduino-mega2560/scripts/flash.ld", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "qemu-armv8a", + "board_config": "qemu-armv8a:nsh", + "elf": "nuttx", + "ld": "", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "mirtoo", + "board_config": "mirtoo:nsh", + "elf": "nuttx", + "ld": "boards/mips/pic32mx/mirtoo/scripts/pinguino-debug.ld", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "-d MIPS32_TOOLCHAIN_GNU_ELF -e MIPS32_TOOLCHAIN_PINGUINOL" + }, + { + "target_name": "hifive1-revb", + "board_config": "hifive1-revb:nsh", + "elf": "nuttx", + "ld": "boards/risc-v/fe310/hifive1-revb/scripts/ld.script", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "rx65n-rsk2mb", + "board_config": "rx65n-rsk2mb:nsh", + "elf": "nuttx", + "ld": "boards/renesas/rx65n/rx65n-rsk2mb/scripts/linker_script.ld", + "map_file": "", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "s698pm-dkit", + "board_config": "s698pm-dkit:nsh", + "elf": "nuttx", + "ld": "", + "map_file": "", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "qemu-intel64", + "board_config": "qemu-intel64:nsh", + "elf": "nuttx", + "ld": "", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + }, + { + "target_name": "esp32-devkitc", + "board_config": "esp32-devkitc:nsh", + "elf": "nuttx", + "ld": "boards/xtensa/esp32/common/scripts/flat_memory.ld.tmp boards/xtensa/esp32/common/scripts/esp32_sections.ld.tmp", + "map_file": "nuttx.map", + "linker_vars": "", + "config_overrides": "" + } +] diff --git a/.github/workflows/membrowse-comment.yml b/.github/workflows/membrowse-comment.yml new file mode 100644 index 0000000000000..05a578a533cfe --- /dev/null +++ b/.github/workflows/membrowse-comment.yml @@ -0,0 +1,42 @@ +name: MemBrowse PR Comment + +on: + workflow_run: + workflows: [MemBrowse Memory Report] + types: + - completed + +jobs: + comment: + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion != 'cancelled' + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Install membrowse + run: pip install --no-cache-dir membrowse + + - name: Post combined PR comment + if: ${{ env.MEMBROWSE_API_KEY != '' }} + env: + MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }} + MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + SUMMARY_ARGS=("$HEAD_SHA" --api-key "$MEMBROWSE_API_KEY" --json) + if [ -n "$MEMBROWSE_API_URL" ]; then + SUMMARY_ARGS+=(--api-url "$MEMBROWSE_API_URL") + fi + membrowse summary "${SUMMARY_ARGS[@]}" > /tmp/membrowse-summary.json + python -m membrowse.utils.github_comment --summary-json /tmp/membrowse-summary.json diff --git a/.github/workflows/membrowse-onboard.yml b/.github/workflows/membrowse-onboard.yml new file mode 100644 index 0000000000000..3ae7af7c421e3 --- /dev/null +++ b/.github/workflows/membrowse-onboard.yml @@ -0,0 +1,98 @@ +name: Onboard to MemBrowse + +on: + workflow_dispatch: + inputs: + num_commits: + description: 'Number of commits to process' + required: true + default: '100' + type: string + +jobs: + load-targets: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v6 + - id: set-matrix + run: echo "matrix=$(jq -c '.' .github/membrowse-targets.json)" >> $GITHUB_OUTPUT + + onboard: + needs: load-targets + runs-on: ubuntu-latest + container: + image: ghcr.io/apache/nuttx/apache-nuttx-ci-linux + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.load-targets.outputs.matrix) }} + + steps: + - name: Checkout nuttx + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Clone nuttx-apps + run: | + git config --global --add safe.directory '*' + git clone --depth=1 https://github.com/apache/nuttx-apps.git ../apps + + - name: Install membrowse + run: python3 -m pip install --no-cache-dir membrowse + + - name: Fetch full git history + run: | + git fetch --unshallow || true + git fetch --all + + - name: Run MemBrowse Onboard + env: + MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }} + MEMBROWSE_API_URL: ${{ vars.MEMBROWSE_API_URL }} + NUM_COMMITS: ${{ github.event.inputs.num_commits }} + TARGET_NAME: ${{ matrix.target_name }} + ELF: ${{ matrix.elf }} + LD: ${{ matrix.ld }} + MAP_FILE: ${{ matrix.map_file }} + LINKER_VARS: ${{ matrix.linker_vars }} + run: | + BUILD_SCRIPT=$(cat <<'BUILD_EOF' + ./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 + find arch -name Makefile -exec sed -i '/DELFILE, $(addsuffix .tmp,$(ARCHSCRIPT))/d' {} + + trap 'git checkout -- arch/' EXIT + make -j$(nproc) + BUILD_EOF + ) + + ARGS=("$NUM_COMMITS" "$BUILD_SCRIPT" "$ELF" "$TARGET_NAME" "$MEMBROWSE_API_KEY") + if [ -n "$MEMBROWSE_API_URL" ]; then + ARGS+=(--api-url "$MEMBROWSE_API_URL") + fi + if [ -n "$LD" ]; then + ARGS+=(--ld-scripts "$LD") + fi + if [ -n "$LINKER_VARS" ]; then + set -f + for var in $LINKER_VARS; do + ARGS+=(--def "$var") + done + set +f + fi + if [ -n "$MAP_FILE" ]; then + ARGS+=(--map-file "$MAP_FILE") + fi + ARGS+=(--binary-search) + + membrowse onboard "${ARGS[@]}" diff --git a/.github/workflows/membrowse-report.yml b/.github/workflows/membrowse-report.yml new file mode 100644 index 0000000000000..9828f6c5aef32 --- /dev/null +++ b/.github/workflows/membrowse-report.yml @@ -0,0 +1,202 @@ +name: MemBrowse Memory Report + +on: + pull_request: + push: + branches: + - master + - "releases/*" + tags: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + 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: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + source: + - '**' + - '!AUTHORS' + - '!CONTRIBUTING.md' + - '!**/CODEOWNERS' + - '!Documentation/**' + - '!tools/ci/docker/linux/**' + - '!tools/codeowners/*' + + load-targets: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v6 + - 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@v6 + with: + fetch-depth: 2 + - uses: actions/setup-python@v6 + 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 }} + 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 + 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@v6 + with: + path: sources/nuttx + fetch-depth: 2 + + - name: Checkout nuttx-apps repo + uses: actions/checkout@v6 + with: + repository: apache/nuttx-apps + path: sources/apps + fetch-depth: 1 + + - name: Docker Login + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.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@v6 + 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 }} + 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 + 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[@]}" diff --git a/README.md b/README.md index 3ec5eba255e7d..40d6c07114e60 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![Contributors](https://img.shields.io/github/contributors/apache/nuttx)](https://github.com/apache/nuttx/graphs/contributors) [![GitHub Build Badge](https://github.com/apache/nuttx/workflows/Build/badge.svg)](https://github.com/apache/nuttx/actions/workflows/build.yml) [![Documentation Badge](https://github.com/apache/nuttx/workflows/Build%20Documentation/badge.svg)](https://nuttx.apache.org/docs/latest/index.html) +[![MemBrowse](https://membrowse.com/badge.svg)](https://membrowse.com/public/apache/nuttx) Apache NuttX is a real-time operating system (RTOS) with an emphasis on standards compliance and small footprint. Scalable from 8-bit to 64-bit