Skip to content
Open
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
40 changes: 39 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ jobs:
build:
name: build (${{ matrix.platform }})
runs-on: ${{ (github.repository == 'commaai/dependencies' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'commaai/dependencies')) && matrix.runs-on-namespace || matrix.runs-on }}
defaults:
run:
shell: ${{ matrix.shell || 'bash -e {0}' }} # the implicit default; a bare `bash` would add pipefail
strategy:
fail-fast: false
matrix:
Expand All @@ -22,6 +25,9 @@ jobs:
- platform: macos
runs-on: macos-latest
runs-on-namespace: namespace-profile-macos-8x14
- platform: windows-x86_64
runs-on: windows-latest
shell: msys2 {0} # the CLANG64 shell from setup-msys2 below
steps:
- uses: actions/checkout@v7
with:
Expand All @@ -34,6 +40,10 @@ jobs:
echo "Selected: $XCODE"
sudo xcode-select -s "$XCODE"
fi
- uses: msys2/setup-msys2@v2
if: runner.os == 'Windows'
with:
msystem: CLANG64
- uses: actions/cache@v6
with:
key: ccache-${{ matrix.platform }}-${{ github.run_id }}
Expand All @@ -46,6 +56,9 @@ jobs:
.uv-cache
*/*/toolchain
*/*/bin
- name: Install MSYS2 build dependencies
if: runner.os == 'Windows'
run: ./setup.sh
- name: Build wheels
env:
MANYLINUX: ${{ runner.os == 'Linux' && '1' || '0' }}
Expand Down Expand Up @@ -88,10 +101,35 @@ jobs:
path: dist/
- run: ./test_wheels_in_image.sh "${{ matrix.image }}"

test-windows:
name: test windows
needs: build
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
name: wheels-windows-x86_64
path: dist/
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install the wheels into a clean venv and run the smoketests
# a stock Python with no MSYS2 on PATH: catches a wheel that still depends on the toolchain's DLLs
shell: bash
run: |
python -m venv venv
source venv/Scripts/activate
pip install dist/*.whl
for toml in */pyproject.toml; do
module="$(basename "$(dirname "$toml")" | tr "-" "_")"
python -c "import $module; $module.smoketest()"
done

publish:
name: publish wheels
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: [build, test]
needs: [build, test, test-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@ mdbook/mdbook/bin/
# vendored at build time
acados/acados/acados_template/
acados/casadi/
acados/casadi-venv/
acados/casadi-target/
acados/casadi-wheel/

# cffi build output on Windows
raylib/Release/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ to add a new package:
* `./build.sh` builds all packages, `./test_wheels_in_image.sh` tests the built wheels in a range of distros
* on pushes to `master`, wheels are built for our target platforms, tested, and published to PyPI

### Windows

`./build.sh` builds the win_amd64 wheels from an [MSYS2](https://www.msys2.org/) CLANG64 shell; `./setup.sh`
installs the pacman packages, git and uv included, so nothing outside MSYS2 is needed.

> [!NOTE]
> PyPI does not allow overwriting an uploaded file, so every master commit publishes
> fresh versions as `<package version>.postN`, where `N` is `git rev-list --count HEAD`
Expand Down
4 changes: 2 additions & 2 deletions acados/acados/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
INCLUDE_DIR = os.path.join(DIR, "include")
LIB_DIR = os.path.join(DIR, "lib")
BIN_DIR = os.path.join(DIR, "bin")
TERA_PATH = os.path.join(BIN_DIR, "t_renderer")
TERA_PATH = os.path.join(BIN_DIR, "t_renderer" + (".exe" if os.name == "nt" else ""))

TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "acados_template")


def smoketest():
import sys

lib_ext = ".dylib" if sys.platform == "darwin" else ".so"
lib_ext = {"darwin": ".dylib", "win32": ".a"}.get(sys.platform, ".so") # acados builds static on Windows
for lib in ("libacados", "libblasfeo", "libhpipm", "libqpOASES_e"):
path = os.path.join(LIB_DIR, lib + lib_ext)
assert os.path.isfile(path), f"missing lib: {path}"
Expand Down
59 changes: 39 additions & 20 deletions acados/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ NJOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)"

# pick BLAS target per host arch
ARCH="$(uname -m)"
if [[ "$OSTYPE" == "darwin"* ]]; then
case "$(uname -s)" in MINGW*|MSYS*) WINDOWS=1 ;; *) WINDOWS="" ;; esac
if [ -n "$WINDOWS" ]; then
# BLASFEO's x86 assembly kernels assume the SysV calling convention; use the C fallback on Windows
BLAS_TARGET="GENERIC"
elif [[ "$OSTYPE" == "darwin"* ]]; then
# this BLASFEO version doesn't have an Apple Silicon target; Cortex-A57
# baseline ARMv8 SIMD compiles and runs fine on M1+.
BLAS_TARGET="ARMV8A_ARM_CORTEX_A57"
Expand All @@ -41,6 +45,9 @@ ACADOS_FLAGS=(
# (pinned) sources keep compiling.
"-DCMAKE_C_FLAGS=-Wno-implicit-function-declaration -Wno-incompatible-pointer-types"
)
if [ -n "$WINDOWS" ]; then
ACADOS_FLAGS+=(-DHPIPM_TARGET=GENERIC)
fi
if [[ "$OSTYPE" == "darwin"* ]]; then
ACADOS_FLAGS+=(
-DCMAKE_OSX_ARCHITECTURES=arm64
Expand All @@ -61,7 +68,7 @@ git -C acados-src submodule update --init --recursive --depth=1
mkdir -p build
cd build
cmake "${ACADOS_FLAGS[@]}" "$DIR/acados-src"
make -j"$NJOBS" install
cmake --build . -j"$NJOBS" --target install
cd "$DIR"

# we don't ship sample json templates
Expand All @@ -86,35 +93,47 @@ if [ -f "$TEMPLATE_DIR/gnsf/check_reformulation.py" ]; then
rm -f "$TEMPLATE_DIR/gnsf/check_reformulation.py.bak"
fi

# render_template() quotes the tera arguments but not the t_renderer path, and
# runs it through cmd.exe (os.system), which strips the command's outer quote
# pair. Quote the executable and wrap the whole command so a spaced path works.
sed -i.bak "s|f\"{tera_path} |f\"'{tera_path}' |" "$TEMPLATE_DIR/utils.py"
sed -i "s|os_cmd = \(os_cmd\.replace.*\)|os_cmd = '\"' + \1 + '\"'|" "$TEMPLATE_DIR/utils.py"
rm -f "$TEMPLATE_DIR/utils.py.bak"

# build tera renderer (needs cargo)
if ! command -v cargo >/dev/null 2>&1; then
if [ -n "$WINDOWS" ]; then
# no rust toolchain requirement on Windows: fetch the release binary acados_template pins
TERA_VERSION="$(sed -n 's/^TERA_VERSION *= *"\(.*\)"/\1/p' "$TEMPLATE_DIR/utils.py" | head -1)"
mkdir -p "$INSTALL_DIR/bin"
curl -fSL -o "$INSTALL_DIR/bin/t_renderer.exe" "https://github.com/acados/tera_renderer/releases/download/v${TERA_VERSION}/t_renderer-v${TERA_VERSION}-windows"
elif ! command -v cargo >/dev/null 2>&1; then
echo "installing rust toolchain (needed for tera_renderer)..."
curl -LsSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
# shellcheck disable=SC1091
source "$HOME/.cargo/env"
fi

mkdir -p "$INSTALL_DIR/bin"
cd "$DIR/acados-src/interfaces/acados_template/tera_renderer/"
if [[ "$OSTYPE" == "darwin"* ]]; then
cargo build --release --target aarch64-apple-darwin
cp target/aarch64-apple-darwin/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
else
cargo build --release
cp target/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
if [ -z "$WINDOWS" ]; then
mkdir -p "$INSTALL_DIR/bin"
cd "$DIR/acados-src/interfaces/acados_template/tera_renderer/"
if [[ "$OSTYPE" == "darwin"* ]]; then
cargo build --release --target aarch64-apple-darwin
cp target/aarch64-apple-darwin/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
else
cargo build --release
cp target/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
fi
cd "$DIR"
fi

cd "$DIR"

# vendor a slim casadi: install the upstream wheel into a throwaway cp312 venv
# vendor a slim casadi: install the upstream wheel into a throwaway directory
# (uv resolves the right platform wheel automatically), then move the casadi/
# tree out and slim it.
echo "vendoring casadi $CASADI_VERSION ..."
rm -rf "$CASADI_DIR" casadi-venv
uv venv --python 3.12 --quiet casadi-venv
uv pip install --python casadi-venv/bin/python --no-deps --quiet "casadi==$CASADI_VERSION"
mv casadi-venv/lib/python3.12/site-packages/casadi "$CASADI_DIR"
rm -rf casadi-venv
rm -rf "$CASADI_DIR" casadi-target
uv pip install --python 3.12 --target casadi-target --no-deps --quiet "casadi==$CASADI_VERSION"
mv casadi-target/casadi "$CASADI_DIR"
rm -rf casadi-target

# drop everything except the bits openpilot actually needs:
# - __init__.py, casadi.py, tools/ (Python wrapper)
Expand All @@ -127,7 +146,7 @@ rm -rf casadi-venv
cd "$CASADI_DIR"
shopt -s extglob
# libc++.*.dylib only exists on darwin and is needed by _casadi.so via @rpath
rm -rf !(__init__.py|casadi.py|_casadi.so|tools|libcasadi.*|libc++.*)
rm -rf !(__init__.py|casadi.py|_casadi.*|tools|libcasadi.*|libc++.*|*.dll)
shopt -u extglob

cd "$DIR"
Expand Down
8 changes: 6 additions & 2 deletions acados/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import platform
import shutil
import subprocess
import sys

from setuptools.command.build_py import build_py

Expand All @@ -25,8 +27,8 @@ class BuildAcados(build_py):

def run(self):
pkg_dir = os.path.dirname(os.path.abspath(__file__))
build_script = os.path.join(pkg_dir, "build.sh")
subprocess.check_call(["bash", build_script], cwd=pkg_dir)
# bash from PATH: CreateProcess would find the WSL launcher in System32 first
subprocess.check_call([shutil.which("bash") or "bash", "build.sh"], cwd=pkg_dir, env={**os.environ, "PYTHON": sys.executable})

super().run()

Expand All @@ -52,6 +54,8 @@ def get_tag(self):
plat = os.environ.get("AUDITWHEEL_PLAT", f"linux_{machine}")
elif system == "Darwin":
plat = "macosx_11_0_arm64"
elif system == "Windows":
plat = "win_amd64"
else:
plat = f"{system.lower()}_{machine}"

Expand Down
2 changes: 1 addition & 1 deletion bootstrap-icons/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ rm -rf "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
cp icons-src/bootstrap-icons.svg "$INSTALL_DIR/"

python3 - <<'PY'
"${PYTHON:-python3}" - <<'PY'
from fontTools.ttLib import TTFont

font = TTFont("icons-src/font/fonts/bootstrap-icons.woff")
Expand Down
6 changes: 4 additions & 2 deletions bootstrap-icons/setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import shutil
import subprocess
import sys

from setuptools.command.build_py import build_py

Expand All @@ -9,8 +11,8 @@ class BuildBootstrapIcons(build_py):

def run(self):
pkg_dir = os.path.dirname(os.path.abspath(__file__))
build_script = os.path.join(pkg_dir, "build.sh")
subprocess.check_call(["bash", build_script], cwd=pkg_dir)
# bash from PATH: CreateProcess would find the WSL launcher in System32 first
subprocess.check_call([shutil.which("bash") or "bash", "build.sh"], cwd=pkg_dir, env={**os.environ, "PYTHON": sys.executable})

super().run()

Expand Down
11 changes: 8 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
export CCACHE_DIR="${CCACHE_DIR:-$ROOT_DIR/.ccache}"

# MSYS2 puts its own mingw CPython first on PATH; the wheels target the MSVC CPython that uv manages
case "$(uname -s)" in MINGW*|MSYS*) export UV_PYTHON="${UV_PYTHON:-3.12}" ;; esac

restore_build_versions() {
python3 build_versions.py restore */pyproject.toml
}
Expand Down Expand Up @@ -95,12 +98,14 @@ echo
echo "Running smoketests"

uv venv --allow-existing --quiet "$VENV_DIR"
uv pip install --python "$VENV_DIR/bin/python" --quiet "cffi>=1.17.1" >/dev/null
uv pip install --python "$VENV_DIR/bin/python" --reinstall --no-deps --quiet dist/*.whl >/dev/null
VENV_PYTHON="$VENV_DIR/bin/python"
[[ -e "$VENV_PYTHON" ]] || VENV_PYTHON="$VENV_DIR/Scripts/python.exe" # Windows venv layout
uv pip install --python "$VENV_PYTHON" --quiet "cffi>=1.17.1" >/dev/null
uv pip install --python "$VENV_PYTHON" --reinstall --no-deps --quiet dist/*.whl >/dev/null

for toml in */pyproject.toml; do
module="$(basename "$(dirname "$toml")" | tr '-' '_')"
"$VENV_DIR/bin/python" -c "import $module; $module.smoketest()" >/dev/null
"$VENV_PYTHON" -c "import $module; $module.smoketest()" >/dev/null
done

du -hs dist/* | sort -hr
Expand Down
15 changes: 10 additions & 5 deletions capnproto/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ cd "$DIR"

VERSION="1.0.1"
INSTALL_DIR="$DIR/capnproto/install"
EXE=""
EXE_LINKER_FLAGS=""
# static libc++ so the tools run outside an MSYS2 shell (no libc++.dll on PATH)
case "$(uname -s)" in MINGW*|MSYS*) EXE=".exe"; EXE_LINKER_FLAGS="-static" ;; esac

NJOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)"

Expand All @@ -31,7 +35,8 @@ cmake -S capnproto-src -B "$DIR/build" \
-DCMAKE_CXX_FLAGS="-fPIC" \
-DWITH_OPENSSL=OFF \
-DBUILD_TESTING=OFF \
-DBUILD_SHARED_LIBS=OFF
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_EXE_LINKER_FLAGS="$EXE_LINKER_FLAGS"

cmake --build "$DIR/build" -j"$NJOBS"
cmake --install "$DIR/build"
Expand All @@ -41,9 +46,9 @@ rm -rf "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR"/{bin,lib,include}

# Binaries
cp "$PREFIX/bin/capnp" "$INSTALL_DIR/bin/"
cp "$PREFIX/bin/capnpc-c++" "$INSTALL_DIR/bin/"
ln -sf capnp "$INSTALL_DIR/bin/capnpc"
cp "$PREFIX/bin/capnp$EXE" "$INSTALL_DIR/bin/"
cp "$PREFIX/bin/capnpc-c++$EXE" "$INSTALL_DIR/bin/"
ln -sf "capnp$EXE" "$INSTALL_DIR/bin/capnpc$EXE" # a copy under MSYS2, which has no symlinks by default

# Libraries (only the ones openpilot needs)
cp "$PREFIX/lib/libcapnp.a" "$INSTALL_DIR/lib/"
Expand All @@ -54,7 +59,7 @@ cp -r "$PREFIX/include/capnp" "$INSTALL_DIR/include/"
cp -r "$PREFIX/include/kj" "$INSTALL_DIR/include/"

# Strip binaries and libs
strip "$INSTALL_DIR/bin/capnp" "$INSTALL_DIR/bin/capnpc-c++" 2>/dev/null || true
strip "$INSTALL_DIR/bin/capnp$EXE" "$INSTALL_DIR/bin/capnpc-c++$EXE" 2>/dev/null || true

# Strip unused kj objects from libkj.a (not needed by openpilot)
for obj in filesystem.c++.o main.c++.o test-helpers.c++.o; do
Expand Down
Loading
Loading