Skip to content

feat(build): update workflow to trigger on feat/* and change MSVC set… #346

feat(build): update workflow to trigger on feat/* and change MSVC set…

feat(build): update workflow to trigger on feat/* and change MSVC set… #346

Workflow file for this run

name: build
on:
push:
branches:
- main
- dev
- release/*
- feat/*
workflow_dispatch:
# release.yml builds through this rather than duplicating ~150 lines of
# toolchain and compile steps. Everything here is unsigned by design: code
# signing and the installer live in release.yml, so a push build stays fast
# and needs no secrets at all.
#
# An unsigned payload is complete and runnable but cannot activate — the boot
# requires both the embedded signature and Authenticode, and a build that
# skipped signing has neither. That is intentional, not a gap.
workflow_call:
jobs:
build:
name: build-${{ matrix.platform }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- platform: windows
os: windows-2022
- platform: macos
os: macos-26
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
ref: ${{ github.ref }}
submodules: true
- name: Prepare environment variables
run: |
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
echo "APP_VERSION=$(node -p "require('./package.json').version")" >> "$GITHUB_ENV"
# ─── Toolchains ─────────────────────────────────────────────────────
# Xcode first: Microsoft.macOS.Sdk.net10.0_26.4 requires Xcode 26.x, and
# selecting it before the SDK step keeps that ordering guarantee now that
# the workload is installed as part of setup-dotnet.
- name: Select latest Xcode
if: runner.os == 'macOS'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
# .NET 10 for both Pengu.Windows (AOT) and Pengu.MacOS. The macos
# workload comes from the `workloads` input rather than a separate
# `dotnet workload install` step — it needs v5, v4 has no such input, and
# it installs into the SDK setup-dotnet just laid down so there is no
# sudo involved.
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
workloads: ${{ runner.os == 'macOS' && 'macos' || '' }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9
run_install: |
- recursive: true
# MSBuild + the VS 2022 C++ workload (preinstalled on windows-2022).
# PublishAot relies on the C++ toolchain shipped with the workload.
- name: Setup MSBuild
if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@v3
# Puts cl.exe / link.exe / rc.exe on PATH for the rest of the job. Ninja
# invokes the compiler directly rather than going through MSBuild, so it
# needs the VC environment that a Developer Command Prompt would set.
- name: Setup MSVC environment
if: runner.os == 'Windows'
uses: TheMrMilchmann/setup-msvc-dev@v4
with:
arch: x64
# ─── Build (shared) ─────────────────────────────────────────────────
# Preload bundle is #include'd into core (renderer.cc embeds preload.g.h).
# Build it before core so the header exists.
- name: Build preload
run: pnpm --filter @pengujs/preload build
# ─── Build (Windows) ────────────────────────────────────────────────
# core.dll and boot.dll, both landing in core/bin/x64/Release/.
#
# Built here rather than left to the ProjectReference in
# Pengu.Windows.csproj, because the .NET SDK's MSBuild has no C++ targets
# — $(VCTargetsPath) is unset under `dotnet build`, so a vcxproj reference
# fails outright. That is what SkipNativeBuild exists for, and why the
# publish step below sets it.
#
# CMake + Ninja rather than MSBuild: one configure covers both targets and
# the whole native build takes seconds. core/core.vcxproj and
# core/boot/boot.vcxproj still exist for Visual Studio via pengu.slnx and
# produce identical output to the same directory — if you add a source
# file, add it to both.
- name: Build native (windows)
if: runner.os == 'Windows'
run: |
cmake -S core -B core/build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build core/build
# Unit tests for the CEF-free logic under core/src — range parsing, the
# path sandbox, config parsing. Seconds to run, and they gate the
# artifact: a failure here should stop the build, not ship.
- name: Test native (windows)
if: runner.os == 'Windows'
run: ctest --test-dir core/build --output-on-failure
# AOT-publish the .NET 10 host. The csproj's BuildHubBundle target runs
# `pnpm --filter @pengujs/hub build` and zips packages/hub/dist/ into
# app.dat as part of the build, and the PublishAppDat target adds app.dat
# to ResolvedFileToPublish — so a single dotnet publish produces
# Pengu.exe + app.dat in the output dir.
#
# SkipNativeBuild drops the vcxproj ProjectReferences: the two steps above
# already produced those binaries with a toolchain the SDK's MSBuild does
# not have.
- name: Publish app/Pengu.Windows (AOT)
if: runner.os == 'Windows'
run: |
dotnet publish app/Pengu.Windows \
-c Release \
-r win-x64 \
-p:SkipNativeBuild=true \
--output app-publish
# Clean portable payload: extract the artifact archive anywhere, run Pengu.exe.
# app.dat is embedded inside Pengu.exe via <EmbeddedResource> — no
# separate file ships alongside the exe.
#
# The native pair comes from the C++ output directory rather than from
# app-publish, since SkipNativeBuild means the publish never staged them.
- name: Stage portable payload (windows)
if: runner.os == 'Windows'
run: |
mkdir -p out/
cp app-publish/Pengu.exe out/
cp app-publish/WebView2Loader.dll out/
cp core/bin/x64/Release/core.dll out/
cp core/bin/x64/Release/boot.dll out/
# ─── Build (macOS) ──────────────────────────────────────────────────
# C++ core -> core/bin/core.dylib (the makefile's LIB_OUT_PATH).
- name: Build core (macos)
if: runner.os == 'macOS'
run: make -C core -j release
# Publish the .NET 10 macOS host as AOT + universal (arm64 + x86_64).
# The csproj's <RuntimeIdentifiers> drives multi-arch — no -r flag.
# The macOS workload publishes each RID, lipo's the resulting native
# binaries, and stages the universal Pengu binary plus app.dat inside
# Pengu.app/Contents/{MacOS,Resources}/. Expect 30–60 minutes due to
# AOT compilation + per-RID lipo on a CI runner.
- name: Publish app/Pengu.MacOS (AOT, universal)
if: runner.os == 'macOS'
run: |
dotnet publish app/Pengu.MacOS \
-c Release \
--output app-publish
# Stage two deliverables for the macOS artifact:
# - Pengu-<ver>.pkg — installer with the universal AOT app inside
# - Pengu.app — universal AOT build (x86_64 + arm64, ~28 MB),
# drag-to-Applications form
#
# The .pkg lands in --output (app-publish/). The universal .app
# always lands at bin/Release/net10.0-macos/Pengu.app regardless of
# --output — `--output` only stages the .pkg installer, not the
# bundle. The bin/.../Pengu.app already has full <BundleResource>
# staging (app-icon.icns, core.dylib), lipo'd Mach-O, and the
# workload's codesign applied. The per-RID .apps under
# bin/Release/.../{osx-arm64,osx-x64}/ are intermediate skeletons —
# not used for distribution.
- name: Stage macOS deliverables
if: runner.os == 'macOS'
run: |
mkdir -p out/
cp app-publish/*.pkg out/
cp -R app/Pengu.MacOS/bin/Release/net10.0-macos/Pengu.app out/Pengu.app
codesign --verify --verbose=2 out/Pengu.app
# ─── Artifact ───────────────────────────────────────────────────────
# release.yml picks the Windows one up by the `pengu-*-windows` pattern,
# so the name shape here is load-bearing for that workflow.
- name: Upload portable artifact
uses: actions/upload-artifact@v7
with:
name: pengu-v${{ env.APP_VERSION }}-${{ env.SHORT_SHA }}-${{ matrix.platform }}
path: out/
if-no-files-found: error