diff --git a/Makefile b/Makefile index c9113b29180..86ed438cbcb 100644 --- a/Makefile +++ b/Makefile @@ -217,12 +217,21 @@ ci-nosetup: # Run the fast jobs. # This is designed for developers, to be run often and before submitting code upstream. +# +# These jobs write to separate target-triple subdirectories, so run them +# concurrently once format-check (cheap, fails fast) passes on its own. +# Only default our own -j if the user didn't pass one -- a submake's own +# -j always overrides a parent's instead of merging with it. --output-sync +# needs make >= 4.0 (e.g. macOS's bundled make is 3.81), so only pass it +# if supported, since an unknown flag is a hard error, not a graceful skip. +NPROC := $(or $(shell nproc 2>/dev/null),$(shell sysctl -n hw.ncpu 2>/dev/null),4) +JOBS := $(if $(filter -j% --jobserver%,$(MAKEFLAGS)),,-j$(NPROC)) +OSYNC := $(if $(filter-out 0 1 2 3,$(firstword $(subst ., ,$(MAKE_VERSION)))),--output-sync=target,) .PHONY: prepush -prepush:\ - format-check\ - ci-job-clippy\ - ci-job-syntax\ - licensecheck +prepush: + @$(MAKE) format-check + @$(MAKE) $(OSYNC) $(JOBS) \ + ci-job-clippy ci-job-syntax licensecheck $(call banner,Pre-Push checks all passed!) # Note: Tock runs additional and more intense CI checks on all PRs. # If one of these error, you can run `make ci-job-NAME` to test locally. @@ -401,21 +410,42 @@ ci-job-readme-check: ### ci-runner-github-clippy jobs: +# +# One board per `arch/*` crate actually in use (tools/build/list_arch_boards.sh), +# so clippy checks every architecture's target-specific code without +# checking every board. +# +# Split into sub-targets so `make -j` can run them concurrently instead of +# back-to-back. They share one workspace `target/` dir, so on a clean +# checkout they still partly serialize on `-Zbuild-std`'s lock; cached +# (normal) runs don't. +# +# $(shell) doesn't fail the build on a nonzero exit, so an empty result +# (the script errored, or a future change left it with nothing to say) +# would otherwise silently generate zero board-specific clippy rules +# below instead of failing loudly. Checked in ci-job-clippy's own recipe, +# not a top-level $(error): this assignment runs on every `make` +# invocation regardless of target (Make expands all rule headers up +# front), so failing here would take down unrelated targets like `make +# clean` whenever this script breaks. +CLIPPY_ARCH_BOARDS := $(shell ./tools/build/list_arch_boards.sh) + .PHONY: ci-job-clippy -ci-job-clippy: - $(call banner,CI-Job: Clippy) +ci-job-clippy: ci-job-clippy-workspace $(foreach b,$(CLIPPY_ARCH_BOARDS),ci-job-clippy-$(subst /,-,$(b))) + @test -n "$(CLIPPY_ARCH_BOARDS)" || (echo "error: list_arch_boards.sh returned no boards -- check it succeeded" >&2; exit 1) + +.PHONY: ci-job-clippy-workspace +ci-job-clippy-workspace: + $(call banner,CI-Job: Clippy (workspace)) @cargo clippy -- -D warnings - # Run `cargo clippy` in select boards so we run clippy with targets that - # actually check the arch-specific functions. - # - # - nrf52840dk: cortex-m4 - # - raspberry_pi_pico: cortex-m0 - # - hifive1: riscv - # - qemu_i486_q35: x86 - @cd boards/nordic/nrf52840dk && cargo clippy -- -D warnings - @cd boards/raspberry_pi_pico && cargo clippy -- -D warnings - @cd boards/hifive1 && cargo clippy -- -D warnings - @cd boards/qemu_i486_q35 && cargo clippy -Zjson-target-spec -- -D warnings + +define clippy_arch_board_rule +.PHONY: ci-job-clippy-$(subst /,-,$(1)) +ci-job-clippy-$(subst /,-,$(1)): + $$(call banner,CI-Job: Clippy ($(1))) + @cd boards/$(1) && cargo clippy -- -D warnings +endef +$(foreach b,$(CLIPPY_ARCH_BOARDS),$(eval $(call clippy_arch_board_rule,$(b)))) @@ -427,10 +457,14 @@ ci-job-clippy: # `rustflags` a crate already sets via its own `.cargo/config.toml`. DENY_WARNINGS_CARGO_CONFIG := $(CURDIR)/boards/cargo/deny_warnings.toml +# Calls `cargo check` directly rather than via `$(MAKE) allcheck`: a +# recursive $(MAKE) here bypasses --output-sync=target's buffering (the +# child manages its own output), so under a parallel `prepush` its +# compile output could land in the middle of another job's block. .PHONY: ci-job-syntax ci-job-syntax: $(call banner,CI-Job: Syntax) - @TOCK_CARGO_FLAGS="--config $(DENY_WARNINGS_CARGO_CONFIG)" $(MAKE) allcheck + @cargo check --config $(DENY_WARNINGS_CARGO_CONFIG) .PHONY: ci-job-compilation ci-job-compilation: diff --git a/boards/qemu_i486_q35/.cargo/config.toml b/boards/qemu_i486_q35/.cargo/config.toml index 053c8ea2905..a77c953ce57 100644 --- a/boards/qemu_i486_q35/.cargo/config.toml +++ b/boards/qemu_i486_q35/.cargo/config.toml @@ -7,13 +7,16 @@ include = [ "../../cargo/unstable_flags.toml", ] -[env] -# Relative to crate root, not this file -# TODO: where is the best place to store this config file? -RUST_TARGET_PATH = "." - [build] target = "i486-unknown-none.json" [target.i486-unknown-none] runner = "qemu-system-i386 -cpu 486 -machine q35 -net none -device isa-debug-exit,iobase=0xf4,iosize=0x04 -device virtio-rng-pci,disable-legacy=on -serial stdio -kernel" + +# `target` above is a bare filename, not a path -- cargo only resolves that +# as a custom target spec (rather than requiring a builtin triple name) with +# this enabled. Same mechanism ../../cargo/riscv_flags.toml uses for the +# RISC-V boards' JSON specs, just needed explicitly here since those are +# referenced by relative path instead. +[unstable] +json-target-spec = true diff --git a/boards/qemu_i486_q35/Makefile b/boards/qemu_i486_q35/Makefile index 6ca8ce21842..74fc04fff9a 100644 --- a/boards/qemu_i486_q35/Makefile +++ b/boards/qemu_i486_q35/Makefile @@ -8,10 +8,6 @@ # Skip auto-installing targets with rustup, since we are using a custom target NO_RUSTUP := 1 -# Because we use a custom target with a .json file, we must pass -# `-Zjson-target-spec` to cargo as of roughly January 2026. -CARGO = cargo -Zjson-target-spec - include ../Makefile.common QEMU_CMD := qemu-system-i386 diff --git a/tools/build/list_arch_boards.sh b/tools/build/list_arch_boards.sh new file mode 100755 index 00000000000..d3dc6d7844d --- /dev/null +++ b/tools/build/list_arch_boards.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2026. + +# For each arch/* crate, print one representative board: the +# alphabetically-first board that depends on it, directly or via its chip. +# +# Reasonably aware of arch dependencies (i.e., `cortexm` picked up by `cortexm7`), and +# avoids duplicates where unneeded (but, e.g., `cortexm4` also necessarily repeats `cortexm`). +# +# Variants of another board (extra feature/policy configs, tutorial +# copies) are skipped, same as boards/README.md's own tooling, since +# they're not independent ports and would just be redundant picks. +# Sorted once so the loop below can stop at the first (alphabetical) match. +boards=($(./tools/build/list_boards.sh | sort | grep -vE '^(configurations|tutorials)/')) + +# True if $board depends on arch/$arch, directly or via its chip. +board_depends_on_arch() { + local board="$1" arch="$2" + local board_toml="boards/$board/Cargo.toml" + # Matches a `path = "…/arch/$arch"` dependency line; some Cargo.toml + # files omit the whitespace around `=`. + local pattern="path[[:space:]]*=[[:space:]]*\"[^\"]*/arch/$arch\"" + + grep -qE "$pattern" "$board_toml" && return 0 + + local chip_path chip_toml + # Pulls the path out of each `chips/*` dependency the board declares. + for chip_path in $(grep -oE 'path[[:space:]]*=[[:space:]]*"[^"]*/chips/[^"]*"' "$board_toml" | sed -E 's/.*"(.*)"/\1/'); do + chip_toml="boards/$board/$chip_path/Cargo.toml" + [ -f "$chip_toml" ] && grep -qE "$pattern" "$chip_toml" && return 0 + done + return 1 +} + +# Collected rather than printed as we go, so a failure partway through +# (see below) leaves nothing on stdout instead of an incomplete board list +# -- callers using `$(shell ...)` can't see a nonzero exit status, only +# empty-vs-nonempty output. +found_boards="" + +for arch in $(./tools/build/list_archs.sh); do + found="" + for board in "${boards[@]}"; do + if board_depends_on_arch "$board" "$arch"; then + found="$board" + break + fi + done + if [ -n "$found" ]; then + found_boards="$found_boards$found +" + continue + fi + + # No board uses this arch directly -- fine if another arch crate does + # (e.g. cortex-m, picked up by cortex-m4), since it then gets compiled, + # and so checked, as a side effect of building that arch's own board. + grep -qE "path[[:space:]]*=[[:space:]]*\"\.\./$arch\"" arch/*/Cargo.toml 2>/dev/null && continue + + echo "error: no board or arch crate depends on arch/$arch" >&2 + exit 1 +done + +printf '%s' "$found_boards" | sort -u