Files
iccery-v2-mac/README.md
T
gronod 288b08e3d9
macOS CI / build-and-test (push) Skipped
macOS CI / build-and-test (pull_request) Failing after 42m38s
macOS CI / package (pull_request) Skipped
ci: add GitHub Actions twin of the Gitea macOS workflow
Copy .gitea/workflows/macos.yml to .github with the github.com deltas:
macos-14 (macos-12 is gone), upload-artifact@v4, no private-CA bundle,
and gh release upload instead of the Gitea asset script.
2026-09-14 15:33:13 +00:00

293 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ICCery
Native macOS frontend for printer ICC/ICM profiling. ICCery walks a user from
chart generation through measurement, `colprof`, verification, and ColorSync
install. It is **not** a colour engine.
**End-user guide:** the [repository wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki)
covers every screen (Getting Started through Troubleshooting). This README is
for building, packaging, and contributing.
All measurement, chart generation, and profile mathematics live in the
[Gronod ArgyllCMS 3.5.0 fork](https://git.i3omb.com/gronod/argyllcms), spawned
as AGPLv3 child processes. The GUI never `dlopen`s or links Argyll.
| | |
|---|---|
| Product | ICCery v2 for macOS |
| Bundle | `com.gronod.iccery2` |
| Version | 2.0.0 |
| Floor | macOS 12.0 Monterey, universal `arm64` + `x86_64` |
| Toolchain | Xcode 14.2 / Swift 5.7 (project `SWIFT_VERSION` is 5.0) |
| CI | Gitea Actions `macos-12` runner |
| Default branch | `develop` |
| M6 | Stage 0 calibration, CGATS import, SceneKit gamut viewer, packaging — shipped |
| 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 |
| Licence | Proprietary source in [`LICENCE.md`](LICENCE.md); bundled Argyll sidecars remain AGPLv3 |
## What it does
The wizard is artefact-gated:
1. **Stage 1**`targen``.ti1`
2. **Stage 2**`printtarg``.ti2` + TIFF, unmanaged `lp` spool, bound `NSPrintPanel`
3. **Stage 3**`instlist` + streaming `chartread` (strip / XY / handheld) → `.ti3`, multi-pass average, CIEDE2000
4. **Stage 4**`colprof``.icc` / `.icm`; optional `applycal`; `iccgamut` next to the profile
5. **Stage 5**`profcheck`, verification history, ColorSync user/system install
Plus:
- **Calibrate Printer** — optional `printcal` / `applycal` session under a `CAL_` basename
- **CGATS import** — `.ti3` / `.txt` / `.cgats` / `.csv`
- **Media recipes and presets** — printer + paper + ink bound to a preset and optional `.cal`
- **Spot Read** — live one-patch Lab/XYZ from the instrument
- **Project files** — `.icceryproj` bookmark over folder, basename, recipe, last ΔE
- **Gamut viewer** — SceneKit Lab hull, sRGB overlay, second-profile compare, click-inspect
- **Settings** — default instrument, ΔE good/warning cutoffs, install location, logging
- Signed `.dmg` packaging with a HiDPI Finder background (Monterey through Sonoma)
**Not this product:** display calibration (`dispwin` / `dispread`), i18n,
Windows/Linux print trees, in-process Argyll, App Sandbox.
## Requirements
To **run** a packaged build:
- macOS 12.0 Monterey or later (Intel or Apple silicon)
To **build** on the supported CI/host floor:
- macOS 12 with **Xcode 14.2** (macOS 12 SDK, Swift 5.7)
- [XcodeGen](https://github.com/yonaskolb/XcodeGen) **2.38.0** (Homebrews current
formula needs Xcode 15.3; CI installs the pinned zip via
`scripts/ensure-host-tools.sh`)
- Network once, to fetch Argyll sidecars
- For DMGs: Python 3.9+ and `dmgbuild==1.6.7` in `build/.venv-dmgbuild`
(`INSTALL_DMGBUILD=1 scripts/ensure-host-tools.sh`)
App Sandbox is **off**. Hardened Runtime is **on**. Entitlements live in
`ICCery.entitlements`.
## Build
```bash
git clone https://git.i3omb.com/gronod/iccery-v2-mac.git
cd iccery-v2-mac
git checkout develop
make fetch-argyll # Vendor/Argyll/macos-universal/, ad-hoc signed
make test # xcodegen + xcodebuild build test (host arch)
make universal # ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO
```
Equivalent without Make:
```bash
xcodegen generate
xcodebuild test -scheme ICCery \
-destination 'platform=macOS' \
ARCHS="$(uname -m)"
```
`project.yml` sets `ARCHS: "$(ARCHS_STANDARD)"`. CI and `make test` override
that with `ARCHS="$(uname -m)"` so unit/UI tests build the host slice only.
Fat binaries are `make universal` / `scripts/package-release.sh`.
Sidecars are **not** in git. `scripts/fetch-argyll.sh` pulls the latest (or
`ARGYLL_RELEASE_TAG`) macOS-universal release from `gronod/argyllcms`, extracts
to `Vendor/Argyll/macos-universal/`, ad-hoc signs every Mach-O, and fails if
`codesign -dvv` or the `instlist` marker is missing.
```bash
# optional
export ARGYLL_SERVER_URL=https://git.i3omb.com
export ARGYLL_REPO=gronod/argyllcms
export ARGYLL_RELEASE_TAG=# default: latest
export GITEA_TOKEN=# private releases
```
`make clean` drops `ICCery.xcodeproj`, `DerivedData`, and
`Packages/ICCeryCore/.build`.
Do not open the generated xcodeproj as the source of truth. Edit `project.yml`
and regenerate.
## Release packaging
```bash
scripts/package-release.sh # fetch → sign → universal build → verify → DMG
```
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`.
Sidecars stay ad-hoc signed inside the bundle — the app is never
`codesign --deep`ed.
`dmgbuild` is **not** a test-job dependency. The package job sets
`INSTALL_DMGBUILD=1` so `scripts/ensure-host-tools.sh` creates
`build/.venv-dmgbuild`. On the Monterey runner (Python 3.9) that install uses
`PIP_IGNORE_REQUIRES_PYTHON=1` and pins `pip>=24.3,<26.1` (pip 26.1+ needs
3.10). Missing background art is a hard fail (#95).
Environment variables read by the pipeline:
| Variable | Purpose |
|---|---|
| `GITEA_TOKEN` | private `gronod/argyllcms` release downloads |
| `ARGYLL_SERVER_URL` / `ARGYLL_REPO` / `ARGYLL_RELEASE_TAG` | sidecar release override |
| `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 |
## Layout
```
Sources/ICCery/ SwiftUI + AppKit shell, stage views, workflow VMs
Packages/ICCeryCore/ wizard state, ProcessManager, argv builders,
settings, CGATS, ΔE₀₀ — no NSPrintPanel
Resources/ assets; Argyll reference files (not the tools)
Vendor/Argyll/ fetched sidecars (gitignored)
Tests/ICCeryCoreTests/ argv goldens, parsers, stores
Tests/ICCeryUITests/ fixture / mock-binary UI tests
scripts/ensure-host-tools.sh
scripts/fetch-argyll.sh
scripts/package-release.sh
docs/ functional spec + v2 ticket plan
```
`ICCeryPrintKit` (issue #16, Quartz / AirPrint / TargetPrint) is v2.1 and is
not in this tree.
## Architecture
- **Spawn, never link.** Tools resolve through `BinaryResolver` inside the
bundle / `Vendor` tree. `$PATH` is not searched. `ARGYLL_NOT_INTERACTIVE=1`
is always set.
- **`ProcessManager` actor** owns child lifetime. Streaming tools
(`chartread`, `printtarg`, `colprof`, …) use the event bus; one-shot tools
(`printcal`, `applycal`, CUPS) use `runCaptured`. Exclusive `ProcessID`
leases. Quit path: `q\n`, ~500 ms, kill; `killAll` on terminate.
- **Argv builders** in ICCeryCore (`TargenArgs`, `PrinttargArgs`,
`ChartreadArgs`, `ColprofArgs`, `ApplycalArgs`, `IccgamutArgs`,
`ProfcheckArgs`, `LpArgs`, `SpotReadArgs`, …). UI must not concatenate flags.
- **Atomic artefacts.** Writes go to `*.tmp` then `replaceItemAt`. `applycal`
must not replace the input profile on cancel or non-zero exit.
- **Concurrency.** View models are `@MainActor`. No blocking I/O on the main
actor. Swift 5.7 / macOS 12: `ObservableObject`, not Observation
`@Observable`.
- **Print.** Unmanaged `lp` with ColorSync suppression
(`AP_ColorMatchingMode` / `AP.ColorMatchingMode`). Captured `NSPrintPanel`
options win over derived CUPS keys. Never `lp -o raw`.
- **SwiftUI ViewBuilder.** Xcode 14.2 / Swift 5.7 still has the ten-child
limit. Split large `VStack`/`Group` trees (#146).
## Tests
```bash
# full suite (host arch) — same as CI
xcodebuild test -scheme ICCery \
-destination 'platform=macOS' \
ARCHS="$(uname -m)"
# fat compile-check (not the default test path):
# ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO
# examples
xcodebuild test -scheme ICCery -destination 'platform=macOS' \
-only-testing:ICCeryCoreTests/ChartreadClassifierTests
xcodebuild test -scheme ICCery -destination 'platform=macOS' \
-only-testing:ICCeryUITests/Milestone5UITests
```
CI (`.gitea/workflows/macos.yml` on Gitea, `.github/workflows/macos.yml` on
GitHub) runs `build-and-test` then `package` on
`develop` and on `v*` tags. Tags whose name contains `prerelease` skip the
test job and still package. `pull_request` is wired for **`develop` only**.
The GitHub file is the same pipeline on `macos-14` (github.com retired
`macos-12`), `actions/upload-artifact@v4`, and `gh release upload` for tag
DMGs.
UI tests need an unlocked console (`IOConsoleLocked=false`). Mock Argyll /
CUPS fixtures live under the test bundles; they must not be treated as proof
that a real `.gam` / `.icc` was extracted.
Hardware gates (real instrument, real printer, Gatekeeper-open `.dmg`) are
manual and block release, not compile.
`ArgyllRunnerPrinttargTests.testSuccess` can flake if streaming stdout is
dropped on a fast mock exit; that is a `ProcessManager` drain race, not a
missing fixture.
## Instruments
Detected via bundled `instlist`:
- i1 Pro / i1 Pro 2 (`i1`)
- ColorMunki (`CM`)
- SpyderPrint (`p3`)
- SpectroScan (`SS`)
- DTP20 / 22 / 41 / 51
- XY tables (SpectroScan, i1iO) when the `instlist` name matches
`/spectro\s?scan|i1io/i`
## Docs
| Where | Audience |
|---|---|
| [Wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki) | End users — screens, workflow, troubleshooting |
| [`docs/`](docs/README.md) | Functional spec (normative for implementers) |
| [`AGENTS.md`](AGENTS.md), [`BUILD-PLAN.md`](BUILD-PLAN.md) | Agent / branch rules |
Implementation order in `docs/`:
| Doc | Topic |
|---|---|
| [`docs/01-overview.md`](docs/01-overview.md) | Product and wizard |
| [`docs/03-ipc-and-process-manager.md`](docs/03-ipc-and-process-manager.md) | Spawn / stdin / kill |
| [`docs/04-argyll-binaries.md`](docs/04-argyll-binaries.md) | CLI argv |
| [`docs/06-wizard-and-artefacts.md`](docs/06-wizard-and-artefacts.md) | Gating |
| [`docs/23-assets.md`](docs/23-assets.md) | Icons, DMG chrome |
| [`docs/24-issues-invariants.md`](docs/24-issues-invariants.md) | Bugs that must not return |
| [`docs/26-v2-mac-ticket-plan.md`](docs/26-v2-mac-ticket-plan.md) | Gitea tickets |
| [`docs/PREUAT.md`](docs/PREUAT.md) | Pre-UAT tester kit |
## Git
```
develop # integration; PRs land here unless a milestone branch is announced
main # protected release line (PR from develop)
feat/<issue>-<slug>
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.
`main` is push-protected and requires status check
`macOS CI / build-and-test (push)`. Protected **file** patterns on `main`
block PR merges that touch matching paths — do not set that field to `*`.
## Licence
GUI source: © 2026 Gordon Bolton — see [`LICENCE.md`](LICENCE.md). Viewing
and personal evaluation only unless a separate grant says otherwise.
ArgyllCMS binaries fetched into `Vendor/Argyll/` are **AGPLv3**. They stay
subprocess-isolated (stdin / stdout / stderr only). Linking them, or spawning
via `$PATH`, is a licence break.
## Related
- [User wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki)
- [gronod/argyllcms](https://git.i3omb.com/gronod/argyllcms) — Argyll 3.5.0 fork (`-u` JSON, `instlist`)
- [gronod/ICCery](https://git.i3omb.com/gronod/ICCery) — v1 Tauri application (spec source, not this tree)