Compare commits

...
Author SHA1 Message Date
gronod 9a6da4648f Merge pull request 'test(m9): scroll stage-2 explicitly before per-page print click (#132)' (#133) from feat/132-perpage-scroll into milestone/m9-monterey
macOS CI / package (push) Blocked by required conditions
macOS CI / build-and-test (push) In progress
2026-09-12 13:02:38 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> b5250591ea test(m9): scroll stage-2 explicitly before per-page print click (#132)
btnPrintPage-0 sits at the window's bottom edge on the 1280x800 runner;
XCUI's automatic scroll-to-visible runs but never moves the frame
(run 32209). Scroll the stage-2 ScrollView manually until hittable.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 13:02:24 +01:00
gronod c1ec07f726 Merge pull request 'fix(m9): revert sidebar container a11y id; measure presetSelect frame (#130)' (#131) from feat/130-sidebar-a11y-revert into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 18m59s
macOS CI / package (push) Skipped
2026-09-12 12:37:39 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> a975f1a4e4 fix(m9): revert sidebar container a11y id; measure presetSelect frame (#130)
On SDK 13.1 the 'sidebar' identifier on the root VStack propagates to
every child, erasing openSettingsBtn/openAboutBtn/btnToggleAllHelp
(a11y dump, run 32201). Revert it; the test now compares presetSelect
frame before/after toggling help overlays.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 12:37:21 +01:00
gronod 7c2282a7dd Merge pull request 'test(m9): dump a11y hierarchy to log on UI probe failure (#128)' (#129) from feat/128-axtree-dump into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 3m43s
macOS CI / package (push) Skipped
2026-09-12 12:31:00 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> caeafb22be test(m9): dump a11y hierarchy to log on UI probe failure (#128)
The probe fails on the runner with an apparently empty accessibility
tree; the xcresult artifact is not retrievable via the API. Print
app.debugDescription + window count so the tree lands in the job log.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 12:30:44 +01:00
gronod aac4f10d23 Merge pull request 'ci(m9): upload UI-test xcresult on failure for a11y triage (#126)' (#127) from feat/126-xcresult-on-failure into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 4m8s
macOS CI / package (push) Skipped
2026-09-12 12:23:12 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> b7ac4f92ed ci(m9): upload UI-test xcresult on failure for a11y triage (#126)
UI failures on the self-hosted runner are hard to triage from stdout
alone — the xcresult already contains the a11y hierarchy snapshot and
screenshots. Upload Logs/Test when build-and-test fails.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 12:22:53 +01:00
gronod 3244efd366 Merge pull request 'ci(m9): accept Xcode >= 14 in toolchain assert (#123)' (#124) from feat/123-xcode-ge-14 into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 5m0s
macOS CI / package (push) Skipped
2026-09-12 11:39:47 +01:00
gronod 7a05da3a35 Merge pull request 'fix(m9): stable 'sidebar' a11y identifier for AboutHelp UI test (#122)' (#125) from feat/122-sidebar-a11y-id into milestone/m9-monterey 2026-09-12 11:39:35 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 5134e82e63 fix(m9): stable 'sidebar' a11y identifier for AboutHelp UI test (#122)
Xcode 14.2 / SDK 13.1 does not surface the sidebar root as an AXGroup,
so the groups-containing query in testHelpOverlaysDoNotChangeSidebarHeight
found nothing (run 32029). Give the sidebar root a stable identifier and
query it directly.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 11:39:16 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 7fd93247cb ci(m9): accept Xcode >= 14 in toolchain assert (#123)
The grep on "Xcode 14." pinned CI to the 14.x series, blocking builds
on macOS 14 / Xcode 16 runners. Parse the major version and require
>= 14 instead.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 11:31:43 +01:00
gronod a0fcede454 Merge pull request 'fix(m9): Debug entitlements disable library validation for test host (#119)' (#121) from feat/119-debug-entitlements into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 21m3s
macOS CI / package (push) Skipped
2026-09-12 09:42:16 +01:00
gronod 97eafd11fb fix(m9): Debug entitlements file to disable library validation for test host (#119) 2026-09-12 09:41:11 +01:00
gronod ed7487c2b6 Merge pull request 'fix(m9): ad-hoc sign embedded ICCeryCore framework so test host launches (#119)' (#120) from feat/119-debug-disable-lib-validation into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 3m15s
macOS CI / package (push) Skipped
Reviewed-on: #120
2026-09-12 09:30:54 +01:00
gronod a2e3f11e70 fix(m9): ad-hoc sign embedded ICCeryCore framework so test host launches (#119) 2026-09-12 09:29:40 +01:00
gronod c3c9bbc5ba Merge pull request 'fix(m9): link ICCeryCore statically so the app launches standalone (#117)' (#118) from feat/117-static-iccerycore into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 26s
macOS CI / package (push) Skipped
Reviewed-on: #118
2026-09-12 09:17:22 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> b5683aa36a fix(m9): link ICCeryCore statically so the app launches standalone (#117)
When the app and test bundles share the local ICCeryCore package, Xcode
builds it as a dynamic package-product framework that is not embedded
in ICCery.app — the app crashes in dyld at launch when run outside the
test host (macOS 12 VM crash, run 31985 products). Declare the product
.static so each consumer links it directly.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 09:14:54 +01:00
gronod 707455dcfb Merge pull request 'test(m9): replace MainActor.assertIsolated for Swift 5.7 (#115)' (#116) from feat/115-assertisolated-swift57 into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 2m53s
macOS CI / package (push) Skipped
Reviewed-on: #116
2026-09-12 09:07:06 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 17ee5d6717 test(m9): replace MainActor.assertIsolated for Swift 5.7 (#115)
MainActor.assertIsolated() requires Swift 5.9 (Xcode 15); the macOS 12
CI runner compiles the test bundle with Swift 5.7 and failed run 31985.
Assert Thread.isMainThread instead — it checks the same contract (the
coalesced log sink hops back to the main actor).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 09:06:45 +01:00
gronod 2295bcdbae Merge pull request 'fix(m9): clear printTask without defer for Swift 5.7 (#113)' (#114) from feat/113-printtask-defer-swift57 into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 1m59s
macOS CI / package (push) Skipped
Reviewed-on: #114
2026-09-12 09:01:37 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> c9bff50a1a fix(m9): clear printTask without defer for Swift 5.7 (#113)
Swift 5.7 on the macOS 12 CI runner (Xcode 14.2) rejects mutation of
actor-isolated properties inside defer bodies, failing run 31978 at
PrintSessionViewModel.swift:113 and :144. Clear printTask explicitly at
each exit point of the print task closures.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 09:00:48 +01:00
gronod 1b3d3dd821 Merge pull request 'fix(m9): keep Stage4View under Swift 5.7 ViewBuilder 10-child limit (#111)' (#112) from feat/111-stage4-viewbuilder-limit into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 1m28s
macOS CI / package (push) Skipped
Reviewed-on: #112
2026-09-12 08:55:57 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 55dee0f0cd fix(m9): keep Stage4View under Swift 5.7 ViewBuilder 10-child limit (#111)
The formSection VStack had 11 direct children; Xcode 14.2's ViewBuilder
only provides buildBlock overloads up to 10 arguments, so the CI build
on the macOS 12 runner failed at line 144 with "extra argument in call"
(run 31958). Nest the calibration toggle and conditional file row in an
inner VStack with identical alignment and spacing.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 08:55:12 +01:00
gronod 45fd2b988f Merge pull request 'fix(m9): pin prebuilt xcodegen for macOS 12 CI (#109)' (#110) from feat/109-ci-xcodegen-pin into milestone/m9-monterey
macOS CI / build-and-test (push) Failing after 2m34s
macOS CI / package (push) Skipped
Reviewed-on: #110
2026-09-12 08:35:51 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 7c6ad1e6ce fix(m9): pin prebuilt xcodegen for macOS 12 CI (#109)
brew install xcodegen resolves to a formula requiring Xcode 15.3, which
cannot be installed on the macOS 12 runner (Xcode 14.2), failing run
31390 before project generation. Install a pinned prebuilt XcodeGen
2.38.0 release via scripts/ensure-host-tools.sh, shared by the
build-and-test and package jobs (package-release.sh also runs xcodegen).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-12 08:34:37 +01:00
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 9857ddb4d0 test(m9): retry dropped calibration generate tap in UI test
macOS CI / build-and-test (push) Failing after 48s
macOS CI / package (push) Skipped
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-11 22:38:30 +01:00
gronod 7a6b82816b Merge pull request 'feat(m9): state management and view demotions for macOS 12 (Slice 4)' (#108) from feat/m9-slice4-state-and-views into milestone/m9-monterey 2026-09-11 21:57:55 +01:00
10 changed files with 177 additions and 26 deletions
+55 -6
View File
@@ -21,13 +21,20 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Assert Xcode 14 toolchain
run: xcodebuild -version | grep -E "Xcode 14." || (echo "Unexpected Xcode version" && exit 1)
- name: Ensure host tools
- name: Assert Xcode 14+ toolchain
run: |
command -v xcodegen || brew install xcodegen
python3 -c "import dmgbuild" 2>/dev/null || pip3 install dmgbuild
line="$(xcodebuild -version | head -1)"
major="$(printf '%s' "$line" | sed -n 's/^Xcode \([0-9][0-9]*\)\..*/\1/p')"
if [ -z "$major" ] || [ "$major" -lt 14 ]; then
echo "Unexpected Xcode version: $line" >&2; exit 1
fi
echo "$line"
# Homebrew's xcodegen formula requires Xcode 15.3, which cannot be
# installed on macOS 12 (#109). The script installs a pinned
# prebuilt release instead.
- name: Ensure host tools
run: scripts/ensure-host-tools.sh
- name: Generate Xcode project
run: xcodegen generate --spec project.yml
@@ -44,6 +51,19 @@ jobs:
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_IDENTITY='-'
# Xcode embeds the shared ICCeryCore package framework into the app
# and the test bundle without signing it. Ad-hoc hosts still require
# every loaded dylib to carry a cdhash — dyld killed the test host at
# launch (run 31992) — so sign every embedded copy once the build is
# done (embed steps run after any build script phase) (#119).
- name: Sign package product frameworks
run: |
find "$DERIVED/Build/Products/Debug" -depth -name '*_PackageProduct.framework' -print0 \
| while IFS= read -r -d '' fw; do
echo "signing $fw"
codesign --force --sign - --timestamp=none "$fw"
done
- name: Test unit (ICCeryCoreTests)
run: |
XCTESTRUN="$(find "$DERIVED" -name 'ICCery*.xctestrun' | head -n 1)"
@@ -126,6 +146,30 @@ jobs:
echo "warning: skipping UI tests after repeated runner attach/activate failures"
exit 0
# XCTest stores the a11y hierarchy snapshot and screenshots in the
# xcresult on failure — upload it so UI failures can be triaged
# without access to the runner (#126).
- name: Prepare Node CA bundle (failure path)
if: failure()
run: |
NODE_CA_FILE="/tmp/macos-ca-bundle.pem"
security find-certificate -a -p \
/System/Library/Keychains/SystemRootCertificates.keychain \
/Library/Keychains/System.keychain \
> "$NODE_CA_FILE" 2>/dev/null || true
if [ ! -s "$NODE_CA_FILE" ] && [ -f /etc/ssl/cert.pem ]; then
cp /etc/ssl/cert.pem "$NODE_CA_FILE"
fi
- name: Upload UI test xcresult
if: failure()
uses: actions/upload-artifact@v3
env:
NODE_EXTRA_CA_CERTS: /tmp/macos-ca-bundle.pem
with:
name: ui-test-xcresult
path: build/DerivedData-test/Logs/Test
package:
needs: build-and-test
runs-on: macos-12
@@ -134,6 +178,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
# scripts/package-release.sh runs `xcodegen generate` and dmgbuild;
# see build-and-test for why brew is not used on macOS 12 (#109).
- name: Ensure host tools
run: scripts/ensure-host-tools.sh
- name: Package release
run: scripts/package-release.sh
env:
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- App Sandbox intentionally absent: ICCery must spawn Argyll tools,
read/write user-chosen working directories, and talk to lp/CUPS. -->
<key>com.apple.security.device.usb</key>
<true/>
<!-- Debug only: the shared ICCeryCore package framework embedded in the
test products is ad-hoc signed with no Team ID, so hardened-runtime
library validation kills the test host at launch (run 31992, #119).
Release uses ICCery.entitlements and links the package statically. -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -110,7 +110,8 @@ final class PrintSessionViewModel: ObservableObject {
isPrinting = true
let task = Task { @MainActor [weak self] in
guard let self else { return }
defer { self.printTask = nil }
// `defer` cannot mutate isolated state under Swift 5.7
// (Xcode 14.2 / macOS 12 runner), so clear explicitly (#113).
var printed = 0
for page in result.pages {
do {
@@ -123,6 +124,7 @@ final class PrintSessionViewModel: ObservableObject {
+ error.localizedDescription
)
isPrinting = false
self.printTask = nil
return
}
}
@@ -132,6 +134,7 @@ final class PrintSessionViewModel: ObservableObject {
autoHideAfter: nil
)
isPrinting = false
self.printTask = nil
}
printTask = task
}
@@ -141,7 +144,6 @@ final class PrintSessionViewModel: ObservableObject {
isPrinting = true
let task = Task { @MainActor [weak self] in
guard let self else { return }
defer { self.printTask = nil }
do {
try await spool(page, index: page.index, pageSize: pageSize)
printNotice = Notice(
@@ -156,6 +158,7 @@ final class PrintSessionViewModel: ObservableObject {
)
}
isPrinting = false
self.printTask = nil
}
printTask = task
}
+4
View File
@@ -138,6 +138,9 @@ struct Stage4View: View {
.textFieldStyle(.roundedBorder)
.accessibilityIdentifier("colprofCopyright")
// Nested VStack keeps the parent at the Swift 5.7 ViewBuilder
// 10-child limit (Xcode 14.2 / macOS 12 CI runner, #111).
VStack(alignment: .leading, spacing: 12) {
Toggle("Apply calibration curve", isOn: $model.applyCalibration)
.accessibilityIdentifier("colprofApplyCalibration")
@@ -151,6 +154,7 @@ struct Stage4View: View {
}
}
}
}
.padding(16)
.background(Theme.panel)
}
@@ -21,7 +21,9 @@ final class ProcessRunSupportTests: XCTestCase {
setRunning: { running.append($0) },
resetLog: { resets += 1 },
onLog: { batch in
MainActor.assertIsolated()
// MainActor.assertIsolated() needs Swift 5.9; the runner is
// on Xcode 14.2 (Swift 5.7) (#115).
XCTAssertTrue(Thread.isMainThread)
received.append(contentsOf: batch)
}
) { onLog in
+15 -6
View File
@@ -46,7 +46,12 @@ final class AboutHelpUITests: XCTestCase {
launchApp()
let openAbout = app.buttons["openAboutBtn"]
XCTAssertTrue(openAbout.waitForExistence(timeout: 10))
if !openAbout.waitForExistence(timeout: 10) {
// CI triage (#128): print the a11y tree so an empty or
// unexpected hierarchy shows up directly in the job log.
print("AXTREE-BEGIN windows=\(app.windows.count)\n\(app.debugDescription)\nAXTREE-END")
}
XCTAssertTrue(openAbout.exists)
openAbout.click()
_ = waitFor("aboutVersion", timeout: 10)
@@ -64,14 +69,18 @@ final class AboutHelpUITests: XCTestCase {
let toggle = app.buttons["btnToggleAllHelp"]
XCTAssertTrue(toggle.waitForExistence(timeout: 10))
let sidebar = app.groups.containing(.button, identifier: "openSettingsBtn").element
let before = sidebar.frame
// SDK 13.1 emits no AXGroup for the sidebar root, and an
// identifier on the container clobbers child identifiers
// (#130) measure a stable sidebar child instead.
let sidebarChild = app.descendants(matching: .any)["presetSelect"]
XCTAssertTrue(sidebarChild.waitForExistence(timeout: 10))
let before = sidebarChild.frame
toggle.click()
let after = sidebar.frame
let after = sidebarChild.frame
XCTAssertEqual(before.size.height, after.size.height,
"Toggling global help must not reflow the sidebar height.")
XCTAssertEqual(before, after,
"Toggling global help must not reflow the sidebar.")
XCTAssertTrue(app.descendants(matching: .any)["openSettingsBtn"].exists)
}
}
+12 -1
View File
@@ -198,7 +198,18 @@ final class Milestone3UITests: XCTestCase {
}
XCTAssertTrue(app.buttons["btnPrintAll"].isEnabled)
app.buttons["btnPrintPage-0"].click()
// The gallery cell's Print button can sit at the window's bottom
// edge where XCUI's automatic scroll-to-visible is inert (#132)
// scroll stage-2 explicitly until the hit point is onscreen.
let printPage = app.buttons["btnPrintPage-0"]
let stage2 = app.scrollViews["stage-2"]
let scrollDeadline = Date().addingTimeInterval(10)
while Date() < scrollDeadline, !printPage.isHittable {
stage2.scroll(byDeltaX: 0, deltaY: -1)
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
}
XCTAssertTrue(printPage.isHittable)
printPage.click()
let argv = waitForLpLine()
XCTAssertTrue(argv.contains("AP_ColorMatchingMode"), argv)
XCTAssertTrue(argv.contains("page1.tif"), argv)
@@ -76,8 +76,15 @@ final class Milestone6CalibrationUITests: XCTestCase {
// After generation the wizard should advance to Stage 2 (layout) because
// a CAL_ .ti1 now exists and the session is in calibration mode.
let layout = app.buttons["btnCreateLayout"]
if !layout.waitForExistence(timeout: 25) {
// The generate tap can be dropped while the dashboard is still
// settling after the stage transition; retry once before failing.
if calGenerate.waitForExistence(timeout: 2) {
calGenerate.tap()
}
XCTAssertTrue(layout.waitForExistence(timeout: 25))
}
}
/// A failing calibration targen surfaces the error through the
/// wizard notice and restores the original basename (issue #80).
+10
View File
@@ -19,6 +19,7 @@ targets:
- path: Resources
excludes:
- ICCery.entitlements
- ICCery.Debug.entitlements
- Argyll
- path: Resources/Argyll
type: folder
@@ -62,6 +63,15 @@ targets:
OTHER_SWIFT_FLAGS: ["$(inherited)", "-strict-concurrency=minimal"]
MACOSX_DEPLOYMENT_TARGET: "12.0"
ARCHS: "$(ARCHS_STANDARD)"
# Debug builds sign ad-hoc; hardened-runtime library validation would
# reject the embedded ICCeryCore package framework (no Team ID) when
# the test host launches (run 31992, #119). DISABLE_LIBRARY_VALIDATION
# does not inject the entitlement on Xcode 14.2, so use a dedicated
# Debug entitlements file. Release keeps validation and links the
# package statically anyway.
configs:
Debug:
CODE_SIGN_ENTITLEMENTS: Resources/ICCery.Debug.entitlements
ICCeryCoreTests:
type: bundle.unit-test
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# scripts/ensure-host-tools.sh
#
# Bootstrap host tools needed by CI on the macOS 12 runner:
# - xcodegen: pinned prebuilt release from GitHub (Homebrew's current
# formula requires Xcode 15.3, which cannot be installed on macOS 12).
# - dmgbuild: via pip3 (used by scripts/package-release.sh).
#
# Safe to run repeatedly: existing tools are left alone.
set -eu
XCODEGEN_VERSION="2.38.0"
INSTALL_ROOT="${XCODEGEN_HOME:-$HOME/.local/xcodegen/$XCODEGEN_VERSION}"
echo "==> Ensuring dmgbuild"
python3 -c "import dmgbuild" 2>/dev/null || pip3 install dmgbuild
if command -v xcodegen >/dev/null 2>&1; then
echo "==> xcodegen already on PATH: $(xcodegen --version)"
else
echo "==> Installing xcodegen $XCODEGEN_VERSION (prebuilt)"
TMP="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
ZIP="$TMP/xcodegen-$XCODEGEN_VERSION.zip"
curl -fL --retry 3 \
"https://github.com/yonaskolb/XcodeGen/releases/download/$XCODEGEN_VERSION/xcodegen.zip" \
-o "$ZIP"
rm -rf "$INSTALL_ROOT"
mkdir -p "$INSTALL_ROOT"
# Zip contains xcodegen/{bin/xcodegen,share/xcodegen/SettingPresets};
# XcodeGen resolves its presets relative to the binary, so keep the tree.
unzip -q "$ZIP" -d "$INSTALL_ROOT"
BIN_DIR="$INSTALL_ROOT/xcodegen/bin"
chmod +x "$BIN_DIR/xcodegen"
if [ -n "${GITHUB_PATH:-}" ]; then
echo "$BIN_DIR" >> "$GITHUB_PATH"
fi
PATH="$BIN_DIR:$PATH"
echo "==> Installed: $("$BIN_DIR/xcodegen" --version)"
fi