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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All@@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All@@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All@@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All@@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,10 +14,12 @@ Two components:

**`ASFW.xcodeproj` is GENERATED from the root `project.yml` (XcodeGen).** Never
edit the pbxproj or hand-tune settings in Xcode — change `project.yml` and run
`xcodegen generate` (`./build.sh` does it automatically when xcodegen is
installed). After adding/removing/renaming source files, regenerate and commit
the updated `ASFW.xcodeproj` together with `project.yml`. Output is
deterministic; the generated project stays committed so CI builds without
`xcodegen generate` with the version pinned in `.xcodegen-version`
(`./build.sh` checks an installed xcodegen automatically; use
`--no-xcodegen` only to build the committed project as-is). After
adding/removing/renaming source files, regenerate and commit the updated
`ASFW.xcodeproj` together with `project.yml`. Output is deterministic for the
pinned version; the generated project stays committed so CI builds without
xcodegen. (`ADKVirtualAudioLab/` has its own separate `project.yml`.)

**Primary build (Xcode — required for signing and producing `.dext`):**
Expand Down
51 changes: 44 additions & 7 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,20 +427,57 @@ Build scripts or CMakeLists are for quick testing and creating compile_commands.
### Xcode project is generated (XcodeGen)

`ASFW.xcodeproj` is generated from the root [`project.yml`](project.yml) with
[XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`).
The generated project is committed, so plain checkouts (and CI) build without
XcodeGen installed — but **never edit the pbxproj or project settings in the
Xcode UI**; change `project.yml` instead.
[XcodeGen](https://github.com/yonaskolb/XcodeGen). The generated project is
committed, so plain checkouts (and CI) build without XcodeGen installed — but
**never edit the pbxproj or project settings in the Xcode UI**; change
`project.yml` instead.

**The XcodeGen version is pinned** in [`.xcodegen-version`](.xcodegen-version),
because its output is *not* byte-identical across releases (2.45.4 → 2.46.0
reorders the pbxproj `targets` list) and CI diffs the regenerated project
against the committed one. Homebrew's `xcodegen` floats, so install the pinned
release rather than `brew install xcodegen` — this is the same download, pin,
and checksum CI uses:

```bash
. ./.xcodegen-version
XCODEGEN_TMP="$(mktemp -d)"
XCODEGEN_PREFIX="$HOME/.local"
curl --fail --location --retry 3 \
--output "$XCODEGEN_TMP/xcodegen.zip" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_TMP}/xcodegen.zip" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_TMP/xcodegen.zip" "$XCODEGEN_TMP/release"
mkdir -p "$XCODEGEN_PREFIX"
"$XCODEGEN_TMP/release/xcodegen/install.sh" "$XCODEGEN_PREFIX"
export PATH="$XCODEGEN_PREFIX/bin:$PATH"
hash -r
xcodegen --version
```

The `PATH` update deliberately places the pinned binary before a Homebrew
installation. Add the same `export` command to your shell startup file if you
want future terminal sessions to use the pinned version.

If `xcodegen` isn't on your `PATH` at all, nothing above applies — `build.sh`
skips regeneration entirely and builds the committed project. The pin only
matters once you have *some* `xcodegen` installed, because `build.sh`
regenerates on every build; if that version doesn't match the pin it stops with
an error rather than writing a pbxproj CI would reject. Use `--no-xcodegen` to
build anyway.

After **adding, removing, or renaming source files**, regenerate the project
and commit it together with your change:

```bash
xcodegen generate # ./build.sh does this automatically when xcodegen is installed
xcodegen generate # ./build.sh does this automatically, and refuses to run
# if your xcodegen doesn't match the pin
```

Output is deterministic — regenerating with no changes produces an identical
pbxproj.
Output is deterministic *for a given XcodeGen version* — regenerating with no
changes produces an identical pbxproj. If your machine has a different version
and you only need to build, pass `./build.sh --no-xcodegen` to use the committed
project as-is (added or removed sources will not be picked up).

NOTE: You need an Apple Developer account (paid) and appropriate entitlements — or a free account plus SIP disabled — to build/load the driver on your machine. See Apple's documentation for details: https://developer.apple.com/documentation/driverkit/debugging-and-testing-system-extensions

Expand Down
49 changes: 44 additions & 5 deletions build.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,6 +60,10 @@ SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov"
# See README "SCSI HBA — opt-in".
ENABLE_SCSI=false

# Skip the XcodeGen regeneration in preflight() and build the committed project
# as-is. Escape hatch for a machine whose xcodegen differs from the pin.
NO_XCODEGEN=false

usage() {
cat <<EOF
Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH] [--derived PATH]
Expand All@@ -74,6 +78,11 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH
--scsi Include the SCSI HBA (personality + restricted entitlement);
requires SIP disabled, and is NOT cold-boot-safe without a
powered-on SBP-2 device attached — see README
--no-xcodegen Build against the committed ASFW.xcodeproj without
regenerating it (escape hatch when the locally installed
xcodegen differs from the version pinned in
.xcodegen-version; added/removed sources will NOT be
picked up)
--scheme NAME Override scheme (default: ${SCHEME_NAME})
--config CONFIG Override configuration (default: ${CONFIGURATION})
--arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})
Expand All@@ -93,6 +102,7 @@ while [[ $# -gt 0 ]]; do
--commands) GENERATE_COMMANDS=true; shift;;
--analyze) RUN_ANALYZER=true; shift;;
--scsi) ENABLE_SCSI=true; shift;;
--no-xcodegen) NO_XCODEGEN=true; shift;;
--scheme) SCHEME_NAME="$2"; shift 2;;
--config) CONFIGURATION="$2"; shift 2;;
--arch) ARCH_NAME="$2"; shift 2;;
Expand All@@ -116,10 +126,39 @@ preflight() {
if ! $TEST_ONLY; then
require_cmd xcodebuild
# The Xcode project is generated from project.yml (XcodeGen). Regenerate so
# source globs pick up added/removed files; output is deterministic, so this
# is a no-op when nothing changed. Falls through to the committed project
# when xcodegen isn't installed (e.g. CI runners).
if [[ -f "project.yml" ]] && command -v xcodegen >/dev/null 2>&1; then
# source globs pick up added/removed files; output is deterministic *for a
# given xcodegen version*, so this is a no-op when nothing changed. Falls
# through to the committed project when xcodegen isn't installed (e.g. CI
# runners, which use the pinned release directly).
if [[ -f "project.yml" ]] && ! $NO_XCODEGEN && command -v xcodegen >/dev/null 2>&1; then
# XcodeGen output is not byte-identical across releases, and CI diffs the
# regenerated pbxproj against the committed one. Regenerating with an
# unpinned version silently produces a project that CI will reject, so
# stop here instead — a build missing a newly added source file is much
# harder to diagnose than this message.
if [[ ! -r ".xcodegen-version" ]]; then
err "xcodegen is installed, but the required .xcodegen-version pin is missing or unreadable."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
unset XCODEGEN_VERSION
. ./.xcodegen-version
if [[ -z "${XCODEGEN_VERSION:-}" ]]; then
err ".xcodegen-version does not define XCODEGEN_VERSION."
err "Refusing to regenerate ${PROJECT_NAME}.xcodeproj with an unpinned version."
exit 1
fi
xcodegen_actual="$(xcodegen --version 2>/dev/null | awk '{print $NF}')"
if [[ "$xcodegen_actual" != "$XCODEGEN_VERSION" ]]; then
err "xcodegen ${xcodegen_actual:-<unknown>} is installed, but this repo pins ${XCODEGEN_VERSION}."
err "Regenerating would produce a pbxproj that CI's drift check rejects."
err "Either:"
err " - install the pinned release (copy-paste block in README -> Building ->"
err " 'Xcode project is generated (XcodeGen)'), or"
err " - re-run with --no-xcodegen to build the committed project as-is"
err " (added/removed source files will NOT be picked up)."
exit 1
fi
log "Regenerating ${PROJECT_NAME}.xcodeproj from project.yml..."
xcodegen generate --quiet || { err "xcodegen generate failed"; exit 1; }
fi
Expand DownExpand Up@@ -231,7 +270,7 @@ run_swift_tests() {
if $VERBOSE; then
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1
else
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)' || true
xcodebuild "${XCODEBUILD_ARGS[@]}" 2>&1 | grep -E '(Test Case|passed|failed|error:)'
fi
local test_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,8 @@ if (NOT GTest_FOUND)
endif()

include(GoogleTest)
# Avoid parallel post-build discovery racing on empty GoogleTest JSON output.
set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)

# Define common test settings interface library
add_library(asfw_test_common_interface INTERFACE)
Expand Down