Skip to content
Merged
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
70 changes: 12 additions & 58 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,8 @@ jobs:

# ============================================================
# FreeBSD cross-compile gate (#402)
#
# Why this exists: the FreeBSD spelling of every `#ifdef` in the
# tree used to be compiled in exactly one place, the release
# workflow's build-freebsd leg, which runs only AFTER merge and was
# best-effort. A FreeBSD-only break therefore passed PR CI, passed
# the release, and surfaced only when somebody built on FreeBSD.
# That is how MNT_NODEV (removed in FreeBSD 10, still present on
# macOS and OpenBSD) reached main.
#
# `make ci` cannot catch this on any single host: it compiles only
# the branch of each platform conditional that matches the runner.
# Compiling FOR FreeBSD is the only thing that reads the FreeBSD
# branches, so it has to happen before merge, and it has to be
# required. No continue-on-error here on purpose.
# `make ci` only ever compiles the platform branch matching its
# runner, so this is the only job that reads the FreeBSD #ifdefs.
# ============================================================
ci-freebsd-cross:
name: FreeBSD / cross-compile (x86_64)
Expand All @@ -144,22 +132,17 @@ jobs:
- name: Install toolchain
run: sudo apt-get update && sudo apt-get install -y gcc make bc curl xz-utils

# The pinned zig and the FreeBSD base sysroot (headers + libc) live in the
# sibling aether-crossbuild repo, same as the release leg uses. zig cc does
# not bundle a FreeBSD libc, so the base is what makes FreeBSD headers
# visible to the compile.
# zig cc bundles no FreeBSD libc, so the base sysroot is what makes the
# FreeBSD headers visible. Same source as the release leg.
- name: Checkout aether-crossbuild
uses: actions/checkout@v4
with:
repository: aether-lang-dev/aether-crossbuild
path: crossbuild
fetch-depth: 1

# Cache the downloaded tarballs, not the unpacked trees: get-zig.sh and
# fetch-freebsd-base.sh both skip the download when the tarball is already
# present and then re-extract, and fetch-freebsd-base.sh deletes and
# rebuilds its sysroot dir every run. Keyed on deps.lock, which pins both
# URLs and their checksums, so a hit is always the pinned bytes.
# Tarballs, not unpacked trees: fetch-freebsd-base.sh rm -rf's its sysroot
# dir every run. deps.lock pins both URLs and checksums.
- name: Restore cross-toolchain download cache
id: cross_cache
uses: actions/cache/restore@v4
Expand Down Expand Up @@ -199,46 +182,19 @@ jobs:
FREEBSD=1 \
ZIG="${{ steps.prov.outputs.zig }}" \
AETHER_SYSROOT="${{ steps.prov.outputs.sysroot }}"
# A cross-build that silently produced Linux binaries would pass the
# compile and prove nothing, so confirm the target really is FreeBSD.
# A cross-build that silently emitted Linux binaries would prove nothing.
file build/aetherc build/ae
file build/ae | grep -q 'FreeBSD' || { echo "ae is not a FreeBSD binary"; exit 1; }

# ============================================================
# FreeBSD native build + test (#402) — POST-MERGE
#
# The cross gate above proves the FreeBSD branches COMPILE. It
# cannot run anything: a FreeBSD ELF needs a FreeBSD kernel. This
# job boots a real FreeBSD VM and runs the C unit suite, so the
# runtime, scheduler (kqueue poller) and stdlib are exercised on
# the platform rather than merely type-checked for it.
#
# Why this one runs on push to main and not on pull requests:
# booting a VM, installing packages and building the tree from
# scratch costs tens of minutes, and what it adds over the cross
# gate is RUNTIME divergence, which is far rarer than a compile
# break and rarely PR-specific. The compile-break class, which is
# what actually broke FreeBSD, is fully covered before merge by
# the cross gate. Every merge to main runs this, so coverage is
# continuous rather than scheduled.
#
# What makes this safe as a post-merge check is that it HARD
# FAILS. The MNT_NODEV break did not hide because its check ran
# after merge; it hid because that check was continue-on-error
# and excluded from the release gate, so failing looked exactly
# like passing. Nothing here is allowed to fail quietly.
#
# Scope is `make test` (the 229 C unit tests), not the full
# `make ci`: the .ae suites and examples shell out through the
# freshly built toolchain and would put a multi-hour build inside
# an emulated VM. The C suite is where the platform-specific
# runtime and stdlib code actually lives.
# FreeBSD native build + test (#402)
# Scoped to `make test`; the .ae suites would rebuild through the
# freshly built toolchain and take hours inside the VM.
# ============================================================
ci-freebsd-native:
name: FreeBSD / native build + unit tests
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 30

steps:
- uses: actions/checkout@v4
Expand All @@ -248,9 +204,7 @@ jobs:
with:
release: '14.2'
usesh: true
# FreeBSD base ships clang, bc and BSD make; this tree needs GNU make,
# and pkgconf for the Makefile's optional-library probes. bc is NOT a
# package here (it lives in base), so asking pkg for it fails the run.
# bc lives in FreeBSD base, not in pkg: asking for it fails the run.
prepare: |
pkg install -y gmake pkgconf
run: |
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`main`, the release pipeline automatically replaces `[current]` with the
next version number before tagging the release.

## [current]

### Changed

- **The FreeBSD native build-and-test job now runs on every pull request**,
not only on merges to main. It was put behind the merge on the assumption
that a VM boot plus a from-scratch build would cost tens of minutes; measured
on its first real run it is 1.6 minutes end to end, including 229/229 unit
tests. There is no reason to find out after merge what can be known before.

### Fixed

- **The build no longer assumes gcc exists.** `CC` was hardcoded to `gcc`, so a
native build on FreeBSD, whose base ships clang as `cc` and carries no gcc at
all, died with `gcc: No such file or directory` before compiling anything.
`CC` now prefers gcc, then the system `cc`, then clang. Platforms that build
today are unaffected: they all have gcc, macOS included, via its clang shim.
Caught by the new FreeBSD native CI job (#402) on its first real run.

## [0.474.0]

### Added
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,15 @@ $(VERSION_HEADER): VERSION Makefile
.PHONY: gen-version-header
gen-version-header: $(VERSION_HEADER)

# Compiler configuration with ccache support
# Compiler configuration with ccache support.
# FreeBSD base has no gcc at all (clang is installed as `cc`).
ifdef WINDOWS_NATIVE
CC := gcc
else
CC := $(shell command -v ccache >/dev/null 2>&1 && echo "ccache gcc" || echo "gcc")
BASE_CC := $(shell if command -v gcc >/dev/null 2>&1; then echo gcc; \
elif command -v cc >/dev/null 2>&1; then echo cc; \
else echo clang; fi)
CC := $(shell command -v ccache >/dev/null 2>&1 && echo "ccache $(BASE_CC)" || echo "$(BASE_CC)")
endif
EXTRA_CFLAGS ?=
PLATFORM ?= native
Expand Down
19 changes: 19 additions & 0 deletions runtime/aether_version.h.tmp.31327
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* Aether SDK version — GENERATED by the Makefile from $(VERSION).
* Do not edit by hand. Dependency-free (#defines only) so anything,
* including the --emit=csrc prologue, can include it cheaply. This is
* the static, drift-proof complement to AetherLibMeta.aether_version
* (per-artifact runtime metadata). */
#ifndef AETHER_VERSION_H
#define AETHER_VERSION_H

#define AETHER_VERSION "0.0.0"
#define AETHER_VERSION_MAJOR
#define AETHER_VERSION_MINOR
#define AETHER_VERSION_PATCH
/* MAJOR*1000000 + MINOR*1000 + PATCH — monotonic, for #if gating:
* #if AETHER_VERSION_NUM < 390000
* # error "requires Aether >= 0.390"
* #endif */
#define AETHER_VERSION_NUM

#endif /* AETHER_VERSION_H */
Loading