Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a45d9c7d | ||
|
|
bb3cc3c858 | ||
|
|
eb73bf0106 | ||
|
|
736fd080cb | ||
|
|
14cd544e05 |
@@ -17,9 +17,16 @@ jobs:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DERIVED: build/DerivedData-test
|
||||
# Tag name on tag pushes, branch name otherwise — scripts/version.sh
|
||||
# only honours values matching 'v[0-9]*'.
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history + tags: scripts/version.sh needs `git describe` and
|
||||
# `git rev-list --count`; the default depth-1 clone has neither.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Assert Xcode 14+ toolchain
|
||||
run: |
|
||||
@@ -51,6 +58,10 @@ jobs:
|
||||
- name: Build for testing (host arch)
|
||||
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
|
||||
run: |
|
||||
# Stamp the test build with the same version triple packaging uses
|
||||
# so CI-built apps are traceable (and mis-tags fail early) (#189).
|
||||
eval "$(scripts/version.sh)"
|
||||
echo "version=$MARKETING_VERSION build=$CURRENT_PROJECT_VERSION tag=$ICCERY_RELEASE_TAG"
|
||||
xcodebuild build-for-testing \
|
||||
-scheme ICCery \
|
||||
-destination 'platform=macOS' \
|
||||
@@ -59,7 +70,10 @@ jobs:
|
||||
ARCHS="$(uname -m)" \
|
||||
ONLY_ACTIVE_ARCH=NO \
|
||||
CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGN_IDENTITY='-'
|
||||
CODE_SIGN_IDENTITY='-' \
|
||||
MARKETING_VERSION="$MARKETING_VERSION" \
|
||||
CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \
|
||||
ICCERY_RELEASE_TAG="$ICCERY_RELEASE_TAG"
|
||||
|
||||
# Xcode embeds the shared ICCeryCore package framework into the app
|
||||
# and the test bundle without signing it. Ad-hoc hosts still require
|
||||
@@ -191,6 +205,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history + tags: scripts/version.sh needs `git describe` and
|
||||
# `git rev-list --count`; the default depth-1 clone has neither.
|
||||
fetch-depth: 0
|
||||
|
||||
# scripts/package-release.sh runs `xcodegen generate` and dmgbuild;
|
||||
# see build-and-test for why brew is not used on macOS 12 (#109).
|
||||
@@ -202,6 +220,7 @@ jobs:
|
||||
- name: Package release
|
||||
run: scripts/package-release.sh
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||
DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
|
||||
|
||||
@@ -30,9 +30,16 @@ jobs:
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DERIVED: build/DerivedData-test
|
||||
# Tag name on tag pushes, branch name otherwise — scripts/version.sh
|
||||
# only honours values matching 'v[0-9]*'.
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history + tags: scripts/version.sh needs `git describe` and
|
||||
# `git rev-list --count`; the default depth-1 clone has neither.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Assert Xcode 14+ toolchain
|
||||
run: |
|
||||
@@ -64,6 +71,10 @@ jobs:
|
||||
- name: Build for testing (host arch)
|
||||
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
|
||||
run: |
|
||||
# Stamp the test build with the same version triple packaging uses
|
||||
# so CI-built apps are traceable (and mis-tags fail early) (#189).
|
||||
eval "$(scripts/version.sh)"
|
||||
echo "version=$MARKETING_VERSION build=$CURRENT_PROJECT_VERSION tag=$ICCERY_RELEASE_TAG"
|
||||
xcodebuild build-for-testing \
|
||||
-scheme ICCery \
|
||||
-destination 'platform=macOS' \
|
||||
@@ -72,7 +83,10 @@ jobs:
|
||||
ARCHS="$(uname -m)" \
|
||||
ONLY_ACTIVE_ARCH=NO \
|
||||
CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGN_IDENTITY='-'
|
||||
CODE_SIGN_IDENTITY='-' \
|
||||
MARKETING_VERSION="$MARKETING_VERSION" \
|
||||
CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \
|
||||
ICCERY_RELEASE_TAG="$ICCERY_RELEASE_TAG"
|
||||
|
||||
# Xcode embeds the shared ICCeryCore package framework into the app
|
||||
# and the test bundle without signing it. Ad-hoc hosts still require
|
||||
@@ -191,6 +205,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history + tags: scripts/version.sh needs `git describe` and
|
||||
# `git rev-list --count`; the default depth-1 clone has neither.
|
||||
fetch-depth: 0
|
||||
|
||||
# scripts/package-release.sh runs `xcodegen generate` and dmgbuild;
|
||||
# see build-and-test for why brew is not used on macOS 12 (#109).
|
||||
@@ -202,6 +220,7 @@ jobs:
|
||||
- name: Package release
|
||||
run: scripts/package-release.sh
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||
DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
|
||||
NOTARIZE_APPLE_ID: ${{ secrets.NOTARIZE_APPLE_ID }}
|
||||
|
||||
@@ -18,6 +18,9 @@ Vendor/Argyll/
|
||||
# XcodeGen output (regenerate with `make gen`)
|
||||
ICCery.xcodeproj/
|
||||
|
||||
# Local-only developer helpers (never committed)
|
||||
/set-version.sh
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
|
||||
@@ -39,6 +39,13 @@ Empty cwd illegal (#59). Atomic writes = `.tmp` + rename (#213).
|
||||
User-supplied strings via SwiftUI `Text` only (#114).
|
||||
TIFF never rendered directly — host-side PNG preview (#58).
|
||||
|
||||
## Versioning
|
||||
`scripts/version.sh` is the single source: tag/describe → `ICCERY_RELEASE_TAG`
|
||||
(About shows `tag (marketing)`), `MARKETING_VERSION` = strict `X.Y.Z`,
|
||||
`CURRENT_PROJECT_VERSION` = `git rev-list --count HEAD` (#189).
|
||||
`v*` tag builds hard-fail if tag's X.Y.Z ≠ `project.yml` MARKETING_VERSION —
|
||||
bump `project.yml` on `develop` before tagging. CI needs `fetch-depth: 0`.
|
||||
|
||||
## Branching
|
||||
`develop` ← `milestone/mN-<name>` ← `feat/<issue#>-<slug>`.
|
||||
PRs via Gitea MCP. Every issue/PR: `Project/ICCery-v2` + `Feature/*` or `Bug/*` + `Priority/*`.
|
||||
@@ -54,3 +61,14 @@ Universal (`ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO`) is still required for rel
|
||||
2-arg `(PMPrintSession, CFStringRef) -> OSStatus`. Never pass integer `1`.
|
||||
Modes: `AP_ApplicationColorMatching` then `ApplicationColorMatching`.
|
||||
`lp` path and Quartz/`ICCeryPrintKit` path use **different** ColorSync dictionaries. Never mix.
|
||||
|
||||
## Gitea issue dependencies
|
||||
Use the `gitea` MCP (custom build with blocking support — verified working):
|
||||
|
||||
- `issue_write` methods:
|
||||
- `add_dependency` — `blocking_issue` blocks `issue_number`.
|
||||
- `remove_dependency` — removes `blocking_issue` from `issue_number`'s blockers.
|
||||
- `block_issue` / `unblock_issue` — `issue_number` blocks/unblocks `blocked_issue`.
|
||||
- `issue_read` methods: `list_dependencies` (issues blocking N),
|
||||
`list_blocks` (issues N blocks).
|
||||
- All issue numbers are *display numbers*, not db ids.
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
# BUILD-PLAN.md — ICCery v2 Mac
|
||||
|
||||
Spec snapshot: `docs/`. Source of tickets: Gitea milestones M1–M6 + M10 (id 32) + Later.
|
||||
Spec snapshot: `docs/`. Source of tickets: Gitea milestones M1–M6 + M10 (id 32) + M11 (id 34) + Later.
|
||||
|
||||
## Sprint rule
|
||||
Do not start milestone N+1 implementation until milestone N **CI/mock gate** is green.
|
||||
@@ -20,11 +20,12 @@ Hardware gates block *release of that sprint*, not filing, and not starting codi
|
||||
| M8 | Deduplicate & consolidate | 79–86 | (already shipped on `develop`) | N/A |
|
||||
| M9 | macOS 12 / Xcode 14.2 retarget | (milestone/m9-monterey, PR #145) | XCTest + ObservableObject + macos-12 CI | N/A |
|
||||
| M10 | Studio workflow | 146–149 | Media library + spot-read + gamut compare + project file unit/UI smoke | Real printer+paper+.cal; live spot-read; two `.gam`; reopen `.icceryproj` |
|
||||
| M11 | Printer settings completeness & dialog binding | 183, 180, 181, 186 | Quality/media/paper caps parse + `LpArgs` emit + picker UI smoke + panel capture-return | Real Epson: 7 `EPIJ_Qual` options; real Canon Pro9500: English media labels; dialog bound to non-default queue with paper/quality/media/orientation preselected; captured settings reach `lp` argv |
|
||||
| Later | Quartz / TargetPrint | 16 | `ICCeryPrintKit` standalone + seam test | 1:1 on paper vs TIFF |
|
||||
|
||||
M8 and M9 merged to `develop` via PR #104 / #145; M10 starts from `800c980`.
|
||||
M8–M10 are merged to `develop`; M11 assembles on `milestone/m11-print-settings` from `736fd08`.
|
||||
|
||||
Issue **16 is not an M3, M6, or M10 exit gate.**
|
||||
Issue **16 is not an M3, M6, M10, or M11 exit gate.**
|
||||
|
||||
## Branch taxonomy
|
||||
|
||||
|
||||
@@ -20,8 +20,14 @@ public enum ArtefactFiles {
|
||||
bundle: Bundle = .main
|
||||
) -> (version: String, build: String, buildDate: String) {
|
||||
let info = bundle.infoDictionary ?? [:]
|
||||
let version = info["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||
let short = info["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||
let build = info["CFBundleVersion"] as? String ?? "0"
|
||||
var tag = ""
|
||||
if let url = bundle.url(forResource: "ICCeryReleaseTag", withExtension: nil),
|
||||
let raw = try? String(contentsOf: url, encoding: .utf8) {
|
||||
tag = raw
|
||||
}
|
||||
let version = displayVersion(shortVersion: short, releaseTag: tag)
|
||||
|
||||
let url = bundle.executableURL ?? bundle.bundleURL
|
||||
let buildDate: String
|
||||
@@ -37,4 +43,15 @@ public enum ArtefactFiles {
|
||||
|
||||
return (version, build, buildDate)
|
||||
}
|
||||
|
||||
/// About "Version:" string from the release tag + marketing version
|
||||
/// (#189): `v2.0.0-pre2 (2.0.0)`; dedupes to `v2.0.0` on an exact
|
||||
/// release tag; falls back to the short version when untagged.
|
||||
static func displayVersion(shortVersion: String, releaseTag: String) -> String {
|
||||
let tag = releaseTag.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if tag.isEmpty || tag == shortVersion || tag == "v\(shortVersion)" {
|
||||
return tag.isEmpty ? shortVersion : tag
|
||||
}
|
||||
return "\(tag) (\(shortVersion))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ as AGPLv3 child processes. The GUI never `dlopen`s or links Argyll.
|
||||
| M7 | Pre-UAT hardening — shipped |
|
||||
| M8 | Deduplication contracts & UAT-ready hardening (#79–#86) — shipped |
|
||||
| M9 | macOS 12 / Xcode 14.2 retarget (PR #145) — shipped |
|
||||
| M10 | Studio workflow: gamut compare (#147), Spot Read (#148), project files (#149) shipped on `develop`; media library (#146) is in the tree, issue still open |
|
||||
| M10 | Studio workflow: media library (#146), gamut compare (#147), Spot Read (#148), project files (#149) — shipped on `develop` |
|
||||
| M11 | Printer settings completeness & dialog binding (#183, #180, #181, #186) — in flight on `milestone/m11-print-settings` |
|
||||
| Licence | Proprietary source in [`LICENCE.md`](LICENCE.md); bundled Argyll sidecars remain AGPLv3 |
|
||||
|
||||
## What it does
|
||||
@@ -125,7 +126,35 @@ The script builds with a fixed derived data path (`build/DerivedData`), locates
|
||||
`Release/ICCery.app` from it, signs the bundle, recursively verifies every
|
||||
bundled Mach-O sidecar (`scripts/verify-sidecar-signatures.sh`), builds a
|
||||
HiDPI TIFF from `Resources/dmg-background.png` (+ `@2x`) via `tiffutil`, and
|
||||
writes `ICCery-${VERSION}-${BUILD_NUM}.dmg` with `dmgbuild==1.6.7`.
|
||||
writes the DMG with `dmgbuild==1.6.7`.
|
||||
|
||||
### Versioning
|
||||
|
||||
`scripts/version.sh` resolves the version triple and is the single source for
|
||||
packaging and CI:
|
||||
|
||||
- **`ICCERY_RELEASE_TAG`** — `RELEASE_TAG` env when it matches `v[0-9]*` (CI
|
||||
tag builds), else `git describe --tags --always --dirty --match 'v[0-9]*'`.
|
||||
Stamped into the bundle's `ICCeryReleaseTag` resource (a generated
|
||||
Info.plist cannot carry custom keys); the About dialog shows
|
||||
`tag (marketing)` — e.g. `v2.0.0-pre2-grok (2.0.0)` — falling back to the
|
||||
plain version when untagged.
|
||||
- **`MARKETING_VERSION`** (`CFBundleShortVersionString`) — first three numeric
|
||||
components of the tag core. Must equal `project.yml`'s
|
||||
`MARKETING_VERSION` on tag builds — packaging hard-fails on mismatch, so
|
||||
bump `project.yml` on `develop` *before* tagging.
|
||||
- **`CURRENT_PROJECT_VERSION`** (`CFBundleVersion`) — `BUILD_NUMBER` env
|
||||
override, else `git rev-list --count HEAD`. A single monotonically
|
||||
increasing integer per Apple's macOS convention (Mac build numbers never
|
||||
reset per version, unlike iOS).
|
||||
|
||||
Tagged/described DMGs carry the tag: `ICCery-2.0.0-pre2-grok-<build>.dmg`;
|
||||
plain releases keep `ICCery-<ver>-<build>.dmg`.
|
||||
|
||||
Release procedure: bump `MARKETING_VERSION` in `project.yml` on `develop` →
|
||||
merge `develop` → `main` → tag the release commit `vX.Y.Z[-suffix]` → push
|
||||
the tag. CI builds, signs, notarizes (when secrets exist) and attaches the
|
||||
DMG to the release.
|
||||
|
||||
Sidecars stay ad-hoc signed inside the bundle — the app is never
|
||||
`codesign --deep`ed.
|
||||
@@ -145,6 +174,8 @@ Environment variables read by the pipeline:
|
||||
| `CODESIGN_IDENTITY` | Developer ID identity for the outer `.app`; unset or `-` = ad-hoc |
|
||||
| `DEVELOPMENT_TEAM` | team ID passed to `xcodebuild` when signing |
|
||||
| `NOTARIZE_APPLE_ID` / `NOTARIZE_PASSWORD` / `APPLE_TEAM_ID` | `notarytool` + staple when all three are set |
|
||||
| `RELEASE_TAG` | release tag string (CI sets `github.ref_name`); stamped into the bundle + DMG name |
|
||||
| `BUILD_NUMBER` | `CFBundleVersion` override; default `git rev-list --count HEAD` |
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -268,9 +299,9 @@ fix/<issue>-<slug>
|
||||
```
|
||||
|
||||
Open feature/fix PRs against **`develop`**. A `milestone/m…` integration
|
||||
branch is used only while that milestone is assembling; `milestone/m10-studio`
|
||||
has been merged and deleted. Do not open umbrella “bugfix” branches that mix
|
||||
tickets.
|
||||
branch is used only while that milestone is assembling — currently
|
||||
`milestone/m11-print-settings` (`milestone/m10-studio` was merged and
|
||||
deleted). Do not open umbrella “bugfix” branches that mix tickets.
|
||||
|
||||
`main` is push-protected and requires status check
|
||||
`macOS CI / build-and-test (push)`. Protected **file** patterns on `main`
|
||||
|
||||
@@ -102,6 +102,62 @@ final class TiffPreviewTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
final class AppInfoTests: XCTestCase {
|
||||
// MARK: displayVersion (#189)
|
||||
|
||||
func testNoTagFallsBackToShortVersion() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(shortVersion: "2.0.0", releaseTag: ""),
|
||||
"2.0.0"
|
||||
)
|
||||
}
|
||||
|
||||
func testWhitespaceTagFallsBackToShortVersion() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(shortVersion: "2.0.0", releaseTag: " "),
|
||||
"2.0.0"
|
||||
)
|
||||
}
|
||||
|
||||
func testExactReleaseTagDedupes() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(shortVersion: "2.0.0", releaseTag: "v2.0.0"),
|
||||
"v2.0.0"
|
||||
)
|
||||
}
|
||||
|
||||
func testBareVersionTagDedupes() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(shortVersion: "2.0.0", releaseTag: "2.0.0"),
|
||||
"2.0.0"
|
||||
)
|
||||
}
|
||||
|
||||
func testPrereleaseTagShowsMarketingInParens() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(
|
||||
shortVersion: "2.0.0", releaseTag: "v2.0.0-pre2-grok"),
|
||||
"v2.0.0-pre2-grok (2.0.0)"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescribeStringShowsMarketingInParens() {
|
||||
XCTAssertEqual(
|
||||
ArtefactFiles.displayVersion(
|
||||
shortVersion: "2.0.0", releaseTag: "v2.0.0-5-gdeadbee"),
|
||||
"v2.0.0-5-gdeadbee (2.0.0)"
|
||||
)
|
||||
}
|
||||
|
||||
func testAppInfoUsesComposedVersion() {
|
||||
// .main in the test host is the ICCery app under test; whatever it
|
||||
// resolves to, build must be a non-empty digits-or-default string.
|
||||
let info = ArtefactFiles.appInfo()
|
||||
XCTAssertFalse(info.version.isEmpty)
|
||||
XCTAssertFalse(info.build.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
final class ArtefactFilesTests: XCTestCase {
|
||||
func testBase64RoundTrip() throws {
|
||||
let url = tempURL("a.txt")
|
||||
|
||||
@@ -69,7 +69,7 @@ Keyboard: **R** resets gamut camera when Stage 5 is visible. Bind to a focusable
|
||||
| Dialog | Root id | Controls |
|
||||
|--------|---------|----------|
|
||||
| Settings | `settingsDialog` | `settingsDeltaEGood`, `settingsDeltaEWarning`, `settingsCalStaleDays`; other controls listed in [22](22-settings-presets.md) |
|
||||
| About | `aboutDialog` | `aboutVersion`, `aboutBuildDate` from `get_app_info`, `closeAboutBtn` |
|
||||
| About | `aboutDialog` | `aboutVersion` (release tag + marketing, e.g. `v2.0.0-pre2 (2.0.0)`), `aboutBuildDate` from `get_app_info`, `closeAboutBtn` |
|
||||
| Save preset | `savePresetDialog` | `savePresetName`, `savePresetDesc`, `btnConfirmSavePreset`, `btnCloseSavePresetDialog` |
|
||||
| Manage presets | `managePresetsDialog` | `managePresetsList`, `btnExportActivePreset`, `btnImportPreset`, `btnCloseManagePresetsDialog` |
|
||||
| Save media recipe | `saveMediaRecipeDialog` | `saveMediaName`, `saveMediaNotes`, `saveMediaPaper`, `saveMediaInk`, `saveMediaPrinter`, `saveMediaPreset`, `saveMediaColourSpace`, `saveMediaCal`, `saveMediaApplyCal`, `btnConfirmSaveMedia`, `btnCloseSaveMediaDialog` |
|
||||
|
||||
+4
-1
@@ -43,6 +43,8 @@ Do not install Argyll to `$PATH`. ICCery never searches `$PATH` for binaries.
|
||||
| `NOTARIZE_APPLE_ID` | Apple ID for `notarytool` |
|
||||
| `NOTARIZE_PASSWORD` | App-specific password for `notarytool` |
|
||||
| `APPLE_TEAM_ID` | Team ID for `notarytool` |
|
||||
| `RELEASE_TAG` | Optional. Simulates a tag build (`vX.Y.Z[-suffix]`); stamped into the bundle and DMG name |
|
||||
| `BUILD_NUMBER` | Optional `CFBundleVersion` override; default `git rev-list --count HEAD` |
|
||||
|
||||
Invocation for a signed, notarized DMG:
|
||||
|
||||
@@ -56,7 +58,8 @@ export APPLE_TEAM_ID="TEAMID"
|
||||
scripts/package-release.sh
|
||||
```
|
||||
|
||||
The script will emit `ICCery-<version>-<build>.dmg`. After mounting, verify Gatekeeper acceptance:
|
||||
The script will emit `ICCery-<version>-<build>.dmg` (or `ICCery-<tag>-<build>.dmg`
|
||||
when `RELEASE_TAG`/a `v*` git tag applies). After mounting, verify Gatekeeper acceptance:
|
||||
|
||||
```
|
||||
spctl -a -t open --context context:primary-signature -v ICCery-*.dmg
|
||||
|
||||
+16
@@ -42,6 +42,15 @@ targets:
|
||||
echo "note: Vendor/Argyll absent — run scripts/fetch-argyll.sh"
|
||||
fi
|
||||
basedOnDependencyAnalysis: false
|
||||
- name: Stamp release tag
|
||||
# GENERATE_INFOPLIST_FILE writes Info.plist after script phases and
|
||||
# only honours Apple's INFOPLIST_KEY_* allowlist — so the tag ships as
|
||||
# a bundled resource read by ArtefactFiles.appInfo (#189).
|
||||
script: |
|
||||
DEST="$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH"
|
||||
mkdir -p "$DEST"
|
||||
printf '%s' "${ICCERY_RELEASE_TAG:-}" > "$DEST/ICCeryReleaseTag"
|
||||
basedOnDependencyAnalysis: false
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.gronod.iccery2
|
||||
@@ -54,6 +63,13 @@ targets:
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright: "Copyright © 2026 Gronod. AGPLv3."
|
||||
MARKETING_VERSION: "2.0.0"
|
||||
CURRENT_PROJECT_VERSION: "1"
|
||||
# Stamped by scripts/version.sh at packaging/CI time (release tag or
|
||||
# `git describe` string); About shows it next to the marketing version.
|
||||
# Written to the bundled ICCeryReleaseTag resource by the
|
||||
# "Stamp release tag" phase — GENERATE_INFOPLIST_FILE drops custom
|
||||
# INFOPLIST_KEY_* names and generates the plist after script phases.
|
||||
# Tag builds hard-fail when the tag's X.Y.Z != MARKETING_VERSION above.
|
||||
ICCERY_RELEASE_TAG: ""
|
||||
ENABLE_HARDENED_RUNTIME: YES
|
||||
CODE_SIGN_ENTITLEMENTS: Resources/ICCery.entitlements
|
||||
CODE_SIGN_IDENTITY: "-"
|
||||
|
||||
@@ -45,6 +45,13 @@ DEST="platform=macOS"
|
||||
IDENTITY="${CODESIGN_IDENTITY:--}"
|
||||
DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM:-}"
|
||||
|
||||
# Resolve the version triple from the release tag / git state. On tag builds
|
||||
# (RELEASE_TAG=v*) this hard-fails if the tag's X.Y.Z != project.yml
|
||||
# MARKETING_VERSION, so a mis-tag cannot ship mismatched metadata (#189).
|
||||
echo "==> Resolving version"
|
||||
eval "$(scripts/version.sh)"
|
||||
echo "version=$MARKETING_VERSION build=$CURRENT_PROJECT_VERSION tag=$ICCERY_RELEASE_TAG"
|
||||
|
||||
echo "==> Building universal Release app"
|
||||
BUILD_EXTRA=""
|
||||
if [ -n "$DEVELOPMENT_TEAM" ]; then
|
||||
@@ -60,6 +67,9 @@ xcodebuild \
|
||||
ONLY_ACTIVE_ARCH=NO \
|
||||
CODE_SIGNING_ALLOWED=YES \
|
||||
CODE_SIGN_IDENTITY="$IDENTITY" \
|
||||
MARKETING_VERSION="$MARKETING_VERSION" \
|
||||
CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \
|
||||
ICCERY_RELEASE_TAG="$ICCERY_RELEASE_TAG" \
|
||||
$BUILD_EXTRA \
|
||||
build
|
||||
|
||||
@@ -142,7 +152,14 @@ fi
|
||||
echo "==> Building DMG"
|
||||
VERSION="$(plutil -extract CFBundleShortVersionString raw "$APP/Contents/Info.plist" 2>/dev/null || printf '2.0.0')"
|
||||
BUILD_NUM="$(plutil -extract CFBundleVersion raw "$APP/Contents/Info.plist" 2>/dev/null || printf '1')"
|
||||
DMG="ICCery-${VERSION}-${BUILD_NUM}.dmg"
|
||||
# Tagged/described builds carry the tag in the DMG name so prerelease
|
||||
# artefacts are self-describing (ICCery-2.0.0-pre2-grok-236.dmg); a plain
|
||||
# release tag keeps the classic ICCery-<ver>-<build>.dmg form.
|
||||
TAG_NAME="${ICCERY_RELEASE_TAG#v}"
|
||||
case "$TAG_NAME" in
|
||||
''|"$VERSION"|dev) DMG="ICCery-${VERSION}-${BUILD_NUM}.dmg" ;;
|
||||
*) DMG="ICCery-${TAG_NAME}-${BUILD_NUM}.dmg" ;;
|
||||
esac
|
||||
VOLUME_NAME="ICCery ${VERSION}"
|
||||
|
||||
DMG_APP="$APP" \
|
||||
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
# scripts/version.sh
|
||||
#
|
||||
# Resolve the ICCery version triple and print KEY=value lines for eval:
|
||||
#
|
||||
# ICCERY_RELEASE_TAG release tag / describe string stamped into the
|
||||
# bundle's ICCeryReleaseTag Info.plist key and shown
|
||||
# in the About dialog.
|
||||
# MARKETING_VERSION CFBundleShortVersionString — strict X.Y.Z only
|
||||
# (Apple forbids suffixes; tag payload never lands here).
|
||||
# CURRENT_PROJECT_VERSION CFBundleVersion — monotonically increasing integer.
|
||||
# macOS/App-Store convention: never reset per version.
|
||||
#
|
||||
# Resolution:
|
||||
# RELEASE_TAG env matching 'v[0-9]*' wins (CI tag builds; branch pushes pass
|
||||
# the branch name and are ignored). Otherwise `git describe` on the worktree.
|
||||
# Marketing version = first three numeric components of the tag core; a tag
|
||||
# with no numeric core falls back to project.yml's MARKETING_VERSION.
|
||||
# Build number = BUILD_NUMBER env override, else `git rev-list --count HEAD`.
|
||||
#
|
||||
# Hard fails (release-tag builds only): tag core not 1-3 numeric components,
|
||||
# or parsed X.Y.Z != project.yml MARKETING_VERSION — bump project.yml or fix
|
||||
# the tag before packaging.
|
||||
#
|
||||
# Usage: eval "$(scripts/version.sh)"
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
YML_MARKETING="$(sed -n 's/^ *MARKETING_VERSION: *"\(.*\)"/\1/p' project.yml | head -n 1)"
|
||||
|
||||
TAG="${RELEASE_TAG:-}"
|
||||
case "$TAG" in
|
||||
v[0-9]*) ;; # real release tag
|
||||
*) TAG="" ;; # branch name (develop, feat/...) or empty
|
||||
esac
|
||||
IS_RELEASE_TAG=0
|
||||
[ -n "$TAG" ] && IS_RELEASE_TAG=1
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="$(git describe --tags --always --dirty --match 'v[0-9]*' 2>/dev/null || true)"
|
||||
fi
|
||||
[ -n "$TAG" ] || TAG="dev"
|
||||
|
||||
# Marketing version: strip leading v, cut at first '-', keep first 3 dot
|
||||
# components. "v2.0.0-pre2" -> "2.0.0"; "v2.0.0.0-x" -> "2.0.0";
|
||||
# "v2.0.0-5-gsha" -> "2.0.0"; bare sha/"dev" -> no numeric core -> fallback.
|
||||
CORE="${TAG#v}"
|
||||
CORE="${CORE%%-*}"
|
||||
MV=""
|
||||
case "$CORE" in
|
||||
*[!0-9.]*|'') ;; # non-numeric core — not a version tag
|
||||
*)
|
||||
MV="$(printf '%s' "$CORE" | cut -d. -f1-3)"
|
||||
# Every kept component must be non-empty digits.
|
||||
case "$MV" in
|
||||
*[!0-9.]*|''|*..*|.*|*.) MV="" ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$IS_RELEASE_TAG" -eq 1 ]; then
|
||||
if [ -z "$MV" ]; then
|
||||
echo "error: release tag '$TAG' has no X.Y.Z numeric core" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$YML_MARKETING" ] && [ "$MV" != "$YML_MARKETING" ]; then
|
||||
echo "error: tag '$TAG' resolves to $MV but project.yml MARKETING_VERSION=$YML_MARKETING; bump project.yml or fix the tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
[ -n "$MV" ] || MV="$YML_MARKETING"
|
||||
[ -n "$MV" ] || MV="0.0.0"
|
||||
|
||||
BUILD="${BUILD_NUMBER:-}"
|
||||
if [ -z "$BUILD" ]; then
|
||||
BUILD="$(git rev-list --count HEAD 2>/dev/null || true)"
|
||||
fi
|
||||
case "$BUILD" in
|
||||
''|*[!0-9]*) BUILD="1" ;;
|
||||
esac
|
||||
[ "$BUILD" -gt 0 ] 2>/dev/null || BUILD="1"
|
||||
|
||||
printf 'ICCERY_RELEASE_TAG=%s\n' "$TAG"
|
||||
printf 'MARKETING_VERSION=%s\n' "$MV"
|
||||
printf 'CURRENT_PROJECT_VERSION=%s\n' "$BUILD"
|
||||
Reference in New Issue
Block a user