Skip to content
Closed
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
55 changes: 55 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# The house C and C++ style, and the one the native gate holds the emitted
# text to (issue #547). clang-format has no canonical style of its own, so the
# style IS this file, and it is written from the estate's own C and C++: the
# serialize runtime the generated code sits beside.
#
# Every option below states a fact about that code rather than a preference:
#
# IndentWidth 4, UseTab Never four spaces, no tabs, everywhere.
# ColumnLimit 0 no reflow. The emitted text carries long
# explanatory trailing comments by design
# (the documentation doctrine), and a
# formatter that rewraps them is a formatter
# fighting the emitter. Line breaks are the
# emitter's; spacing, bracing and alignment
# are this file's.
# PointerAlignment Middle `const char * name`.
# SpacesInParens Custom, Other `f( a, b )` and `if ( x )`, the estate
# spelling; C-style casts stay tight.
# AfterNamespace false `namespace pkg {` on one line, which both
# C++ emitters already write.
# Everything else Allman classes, structs, unions, enums, functions
# and control statements open on their own
# line.
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: Never
ColumnLimit: 0
PointerAlignment: Middle
ReferenceAlignment: Middle
SpacesInParens: Custom
SpacesInParensOptions:
InConditionalStatements: true
InCStyleCasts: false
Other: true
SortIncludes: false
AlignAfterOpenBracket: DontAlign
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
IndentCaseLabels: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
SplitEmptyFunction: false
SplitEmptyRecord: false
149 changes: 149 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ env:
SERIALIZE_CS_TAG: v1.9.1
SERIALIZE_JS_TAG: v1.4.2

# The C and C++ native legs' clang tooling, by EXACT archive version
# (issue #547). ubuntu-24.04 is the archive; the major each leg wants is its
# row's, and this is the version that major resolves to there. Bumping it is
# a deliberate one-line edit, and the version comes from the policy the
# failing install prints.
CLANG_APT_PIN: "1:18.1.3-1ubuntu1"

jobs:
# The compiler's own test suite, and the only job in this file that reads a
# Go source file. `go test ./...` covers internal/fuzz, whose seeded corpus
Expand Down Expand Up @@ -303,6 +310,148 @@ jobs:
- name: the ${{ matrix.lang }} leg over every surface
run: env ${{ matrix.env }} ./build/conformance-harness run --only ${{ matrix.lang }}

# THE NATIVE GATE (issue #547), one job per port row of the matrix (#366).
#
# The law is that what schema emits for a target reads as that language's own
# code. Two of its three parts are machine-checkable and this is where they
# run: the language's standard FORMATTER in check mode, and its standard
# ANALYZER at default strictness, over the GENERATED code of both corpora —
# the examples corpus under generated/, the tables corpus under build/ — red
# on any finding. The third part, the fluent-reader review, is a person's and
# lives on the row's pull request.
#
# WHAT EACH LEG RUNS is `make native-<lang>`, and that target lives in the
# port's own make/<lang>.mk beside its test and conformance legs. So the
# instruments a language is held to are a decision made in that language's
# file, by whoever knows it, rather than a line in this one.
#
# THE MATRIX IS DISCOVERED, NOT TYPED, the same way the conformance matrix
# above is: `test/native/matrix` reads every test/native/<lang>/ci.json, and
# refuses a tree where a port has a conformance driver and no native row. A
# row names the make target, the sibling runtime the analyzer needs on disk
# to typecheck what the emitter wrote, the toolchain steps to install and
# the make overrides that point the leg at them. Registering a port's native
# gate is its make target and its ci.json, and no edit here.
#
# SHARDED PER LEG for the reason the conformance matrix is: a leg's wall is
# its own toolchain's, so one job per language keeps the SLOWEST leg inside
# the rule rather than the SUM of them, and a leg that stops fitting is named
# by the job that missed.
native-matrix:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.discover.outputs.matrix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.26'
cache: false
- id: discover
name: the native registry, as a matrix
run: |
matrix=$(go run ./test/native/matrix)
echo "$matrix"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"

native:
name: native (${{ matrix.lang }})
needs: native-matrix
# NAMED rather than `-latest`, the argument the big-endian and msvc jobs
# make: the C and C++ legs install their tooling by exact package version,
# and a package version is only meaningful against one archive.
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.native-matrix.outputs.matrix) }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# An ANALYZER needs the runtime on disk for the same reason a compiler
# does: it typechecks what the emitter wrote, and generated Go, Rust, C#,
# C and C++ name a sibling. A leg whose generated code names no runtime
# leaves the row's two fields empty and clones nothing.
- name: Check out the sibling runtime this leg needs (pinned release)
if: matrix.runtime != ''
run: |
tag=$(printenv "${{ matrix.runtime_tag }}")
git clone --quiet --depth 1 --branch "$tag" "https://github.com/mas-bandwidth/${{ matrix.runtime }}.git" "../${{ matrix.runtime }}"

# bin/schema and the generated trees come out of Go in every leg.
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.26'
cache: false

- name: read the .NET SDK pin
if: matrix.dotnet != ''
run: echo "DOTNET_SDK_PIN=$(cat .github/dotnet-version)" >> "$GITHUB_ENV"

- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
if: matrix.dotnet != ''
with:
dotnet-version: ${{ env.DOTNET_SDK_PIN }}

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: matrix.node != ''
with:
node-version: ${{ matrix.node }}

# rustfmt and clippy are NAMED here: they are the two instruments this
# leg is, and a toolchain installed without them fails on a missing
# binary rather than on a finding.
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # the stable BRANCH head — the SHA freezes the action, not the compiler: rustup resolves stable at run time
if: matrix.rust != ''
with:
components: rustfmt, clippy

- uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1.8.1
if: matrix.dart != ''
with:
sdk: ${{ matrix.dart }}

- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
if: matrix.java != ''
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}

- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
if: matrix.otp != ''
with:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}

# THE C AND C++ TOOLING, by EXACT package version for the reason the
# big-endian job installs its cross compiler that way: which diagnostics
# clang-tidy has and how clang-format lays a construct out are
# toolchain-version facts, so a leg on a floating version would prove a
# different thing each month. The row names the major and CLANG_APT_PIN
# holds the archive's version; a failing install prints the policy, which
# is where the next pin comes from.
- name: the pinned clang tooling
if: matrix.clang != ''
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
"clang-format-${{ matrix.clang }}=$CLANG_APT_PIN" \
"clang-tidy-${{ matrix.clang }}=$CLANG_APT_PIN" || {
apt-cache policy "clang-format-${{ matrix.clang }}" "clang-tidy-${{ matrix.clang }}"
exit 1
}
clang-format-${{ matrix.clang }} --version
clang-tidy-${{ matrix.clang }} --version

# NATIVE_JOBS is the fan-out the C and C++ legs use for clang-tidy, which
# is one process per translation unit over a couple of hundred of them
# and is the only leg here whose wall is not one tool's.
- name: the ${{ matrix.lang }} formatter and analyzer over the generated code
run: make ${{ matrix.targets }} ${{ matrix.env }} NATIVE_JOBS="$(nproc)"


# DOGFOOD BIG-ENDIAN (issue #303). docs/SPEC-TABLES.md §3 says the wire is
# little-endian and byte-oriented throughout; §7 says a cook is produced in
# the byte order of the build it is cooked for, and that Open refuses any
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,12 @@ erl_crash.dump
# binary here rather than in build/ — the Makefile always names `-o` and never
# wants this file. It reached main once already (#345).
/harness


# the Elixir native gate's credo build and its fetched deps
/test/native/elixir/_build/
/test/native/elixir/deps/

# a working clone symlinks the pinned toolchains in rather than unpacking a
# second copy; the ignore above matches the directory, this one the link
/dist
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3036,3 +3036,62 @@ registry:
@echo "conformance: $(CONFORMANCE_LEGS)"
@echo "bench-tables: $(BENCH_TABLES_LEGS)"
@echo "goldens: $(GOLDENS_LEGS)"
@echo "native: $(NATIVE_LEGS)"

# ---- THE C AND C++ NATIVE GATE (issue #547) --------------------------------
#
# clang-format and clang-tidy are the instruments a C or C++ reader runs, and
# these two legs hold the emitted text of both corpora to them. The style is
# the repository's .clang-format, which is written from the estate's own C and
# C++ and says why each option is what it is; clang-tidy runs its DEFAULT
# check set, which is the reading the law asks for, with every diagnostic an
# error.
#
# THE VERSION IS NAMED, never `clang-format` bare: which diagnostics exist and
# how a construct is formatted are exactly toolchain-version facts, so a leg
# that took whatever a machine had installed would prove a different thing on
# every machine. CI installs this major at an exact package version; a
# workstation that has another one overrides these two variables.
CLANG_MAJOR ?= 18
CLANG_FORMAT ?= clang-format-$(CLANG_MAJOR)
CLANG_TIDY ?= clang-tidy-$(CLANG_MAJOR)

# clang-tidy is one process per translation unit over a couple of hundred units
# per leg, and every one of them is independent.
NATIVE_JOBS ?= 4

# BOTH HALVES RUN, ALWAYS, and the verdict comes at the end. A leg that stopped
# at its formatter would hide its analyzer's findings behind a whitespace diff,
# and the whole finding list is what the emitter is owed.
NATIVE_CPP_DIRS = generated/cpp generated/cpp/ludicrous $(wildcard build/tables-generated/*/)

.PHONY: native-cpp
native-cpp: generated/cpp/.stamp generated/cpp/ludicrous/.stamp build/tables-generated/.stamp
@fail=0; \
echo "==== clang-format"; \
$(CLANG_FORMAT) --dry-run --Werror \
generated/cpp/*.h generated/cpp/ludicrous/*.h \
build/tables-generated/*/*.h build/tables-generated/*/*.cpp || fail=1; \
echo "==== clang-tidy"; \
for d in $(NATIVE_CPP_DIRS); do \
files=$$(ls $$d/*.h $$d/*.cpp 2>/dev/null); \
[ -n "$$files" ] || continue; \
printf '%s\n' $$files | xargs -P $(NATIVE_JOBS) -I{} \
$(CLANG_TIDY) --quiet --warnings-as-errors='*' {} -- \
-xc++ -std=c++17 -I$$d -I$(SERIALIZE) || fail=1; \
done; \
if [ $$fail -ne 0 ]; then echo "native C++: the findings above are the emitter's"; exit 1; fi; \
echo "native C++: clang-format canonical and clang-tidy clean over the examples and tables corpora"

NATIVE_LEGS += native-cpp

# THE NATIVE GATE (issue #547): every registered leg's formatter and analyzer
# over its own generated code. One target per language, registered by that
# language's make/<lang>.mk exactly as its test and conformance legs are, so a
# port lands its native gate by adding the target and nothing else.
#
# CI runs the legs SHARDED, one job each, because a leg's wall is its own
# toolchain's; this target is the whole set, for a workstation with every
# toolchain installed.
.PHONY: native
native: $(NATIVE_LEGS)
8 changes: 6 additions & 2 deletions internal/codegen/dart/dart.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,12 @@ func (g *gen) assemble() []byte {
}
g.emitHelpers(&h)
h.WriteString(strings.TrimRight(g.body.String(), "\n"))
h.WriteString("\n")
return []byte(h.String())
// One trailing newline, with no blank line before it. A unit whose Dart
// surface is the banner and nothing else (no import, no helper, no
// declaration) would otherwise end on the blank line that follows the
// banner, and that blank line is the one thing `dart format` rewrites in
// this emitter's output.
return []byte(strings.TrimRight(h.String(), "\n") + "\n")
}

// writeImport renders one show-list import, wrapped the way dart format
Expand Down
25 changes: 25 additions & 0 deletions make/c.mk
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,28 @@ TEST_LEGS += test-c
CONFORMANCE_LEGS += build/conformance-c
BENCH_TABLES_LEGS += generated/bench/tables/c/.stamp
GOLDENS_LEGS += update-goldens-c

# THE C NATIVE GATE (issue #547), the C++ leg's twin in the Makefile: the same
# two instruments, the same style file, the same pinned major, over the C
# emitter's output for both corpora, both halves always run.
NATIVE_C_DIRS = generated/c generated/c-ludicrous $(wildcard build/tables-generated-c/*/)

.PHONY: native-c
native-c: generated/c/.stamp generated/c-ludicrous/.stamp build/tables-generated-c/.stamp
@fail=0; \
echo "==== clang-format"; \
$(CLANG_FORMAT) --dry-run --Werror \
generated/c/*.h generated/c-ludicrous/*.h \
build/tables-generated-c/*/*.h build/tables-generated-c/*/*.c || fail=1; \
echo "==== clang-tidy"; \
for d in $(NATIVE_C_DIRS); do \
files=$$(ls $$d/*.h $$d/*.c 2>/dev/null); \
[ -n "$$files" ] || continue; \
printf '%s\n' $$files | xargs -P $(NATIVE_JOBS) -I{} \
$(CLANG_TIDY) --quiet --warnings-as-errors='*' {} -- \
-xc -std=c99 -I$$d -I$(SERIALIZE_C) || fail=1; \
done; \
if [ $$fail -ne 0 ]; then echo "native C: the findings above are the emitter's"; exit 1; fi; \
echo "native C: clang-format canonical and clang-tidy clean over the examples and tables corpora"

NATIVE_LEGS += native-c
36 changes: 36 additions & 0 deletions make/cs.mk
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,39 @@ TEST_LEGS += test-cs
CONFORMANCE_LEGS += build-conformance-cs build-cs-cook
BENCH_TABLES_LEGS += generated/bench/tables/cs/.stamp
GOLDENS_LEGS += update-goldens-cs

# THE C# NATIVE GATE (issue #547). Two halves, and they cover different sets
# for a reason that is the emitter's shape rather than a choice here. Both run
# before the leg reports.
#
# THE FORMATTER half is `dotnet format whitespace --verify-no-changes` over the
# generated trees of both corpora. --folder is what lets the formatter read a
# DIRECTORY rather than a project, and that is required here: a generated
# unit's Block and Cook accelerators share one set of blittable records, so no
# single project compiles a whole unit, let alone the whole corpus
# (test/conformance/cs/schemaconformance.csproj excludes them by name for
# exactly this reason). --folder also means whitespace alone; the style and
# analyzer fixers need a workspace, and what they would see is the second
# half's business.
#
# THE ANALYZER half is the .NET analyzers at their default mode, warnings as
# errors, over the two projects that DO compile: the packet corpus through
# test/cs, and the tables corpus through the conformance project. Analyzers are
# on by default for this TargetFramework; naming the properties here says what
# the gate depends on rather than leaving it to an SDK default.
.PHONY: native-cs
native-cs: generated/cs/.stamp generated/cs-ludicrous/.stamp build/tables-generated-cs/.stamp
@fail=0; \
echo "==== dotnet format"; \
dotnet format whitespace generated/cs --folder --verify-no-changes || fail=1; \
dotnet format whitespace generated/cs-ludicrous --folder --verify-no-changes || fail=1; \
dotnet format whitespace build/tables-generated-cs --folder --verify-no-changes || fail=1; \
echo "==== the .NET analyzers"; \
( cd test/cs && dotnet build -v q --nologo \
-p:EnableNETAnalyzers=true -p:AnalysisMode=Default -p:TreatWarningsAsErrors=true ) || fail=1; \
( cd test/conformance/cs && dotnet build -v q --nologo \
-p:EnableNETAnalyzers=true -p:AnalysisMode=Default -p:TreatWarningsAsErrors=true ) || fail=1; \
if [ $$fail -ne 0 ]; then echo "native C#: the findings above are the emitter's"; exit 1; fi; \
echo "native C#: dotnet format canonical and the .NET analyzers clean over the examples and tables corpora"

NATIVE_LEGS += native-cs
Loading
Loading