# Build from source

> Generate InkIt.xcodeproj with XcodeGen, open in Xcode 15+, Debug vs Release signing via Config/*.xcconfig, ad-hoc vs Developer ID notarized builds, Sparkle deep-sign post-build script, and install to /Applications.

- Repository: cartesia-ai/InkIt
- GitHub: https://github.com/cartesia-ai/InkIt
- Human docs: https://grok-wiki.com/public/docs/cartesia-ai-inkit-18975554254b
- Complete Markdown: https://grok-wiki.com/public/docs/cartesia-ai-inkit-18975554254b/llms-full.txt

## Source Files

- `README.md`
- `project.yml`
- `AGENTS.md`
- `Config/Debug.xcconfig`
- `Config/Signing.xcconfig`
- `Config/Signing.local.xcconfig.example`

---

---
title: "Build from source"
description: "Generate InkIt.xcodeproj with XcodeGen, open in Xcode 15+, Debug vs Release signing via Config/*.xcconfig, ad-hoc vs Developer ID notarized builds, Sparkle deep-sign post-build script, and install to /Applications."
---

InkIt ships as a generated `InkIt.xcodeproj` (not committed) built from `project.yml` via XcodeGen. Signing, entitlements, and Sparkle helper re-signing are controlled per configuration through `Config/*.xcconfig`; the Release `.app` lands at `build/Build/Products/Release/InkIt.app` when you pass `-derivedDataPath build`.

## Prerequisites

| Requirement | Value |
|-------------|-------|
| macOS | 14.0+ (`MACOSX_DEPLOYMENT_TARGET` in `project.yml`) |
| Hardware | Apple silicon (product requirement in README) |
| Xcode | 15+ (CI runs on `macos-15`) |
| XcodeGen | `brew install xcodegen` |
| Bundle identifier | `ai.cartesia.InkIt` |
| App Sandbox | Disabled (`ENABLE_APP_SANDBOX = NO` in `project.yml`) |

<Note>
`InkIt.xcodeproj` and `build/` are gitignored. Regenerate the project after cloning or editing `project.yml`; edit `project.yml`, not the generated `.pbxproj`.
</Note>

## Generate the Xcode project

<Steps>
<Step title="Install XcodeGen">

```bash
brew install xcodegen
```

</Step>

<Step title="Generate InkIt.xcodeproj">

From the repository root:

```bash
xcodegen generate
```

XcodeGen reads `project.yml` and writes `InkIt.xcodeproj`, wiring the InkIt app target, `InkItTests`, the Sparkle Swift package (≥ 2.9.2), and per-configuration xcconfig files.

</Step>

<Step title="Open in Xcode">

```bash
open InkIt.xcodeproj
```

Select the **InkIt** scheme. Use **Debug** for development and unit tests; use **Release** for a distributable local build.

</Step>
</Steps>

## Configuration and signing

Each build configuration maps to a dedicated xcconfig. Both include `Config/Sparkle.xcconfig`, which sets `SPARKLE_PUBLIC_ED_KEY` for the `SUPublicEDKey` Info.plist entry.

<Tabs>
<Tab title="Debug">

`Config/Debug.xcconfig` — local development and `xcodebuild test`.

| Setting | Value |
|---------|-------|
| `CODE_SIGN_IDENTITY` | `-` (ad-hoc) |
| `CODE_SIGN_STYLE` | `Manual` |
| `ENABLE_HARDENED_RUNTIME` | `NO` |
| `CODE_SIGN_ENTITLEMENTS` | `InkIt/InkIt-Debug.entitlements` |

Debug entitlements match Release except `com.apple.security.get-task-allow` is `true`, allowing the debugger and XCTest host to attach.

</Tab>

<Tab title="Release">

`Config/Signing.xcconfig` — local Release builds and distribution.

| Setting | Default | With `Signing.local.xcconfig` |
|---------|---------|-------------------------------|
| `CODE_SIGN_IDENTITY` | `-` (ad-hoc) | `Developer ID Application` |
| `ENABLE_HARDENED_RUNTIME` | `YES` | `YES` |
| `CODE_SIGN_ENTITLEMENTS` | `InkIt/InkIt.entitlements` | `InkIt/InkIt.entitlements` |
| `OTHER_CODE_SIGN_FLAGS` | — | `--timestamp --options=runtime` |

Release entitlements set `com.apple.security.get-task-allow` to `false` (required for notarization). Both configurations grant microphone and Apple Events automation.

</Tab>
</Tabs>

### Ad-hoc vs Developer ID

By default, Release builds are **ad-hoc signed** (`CODE_SIGN_IDENTITY = -`). Anyone can compile and run InkIt locally without an Apple Developer account.

For **Developer ID** signing (notarized DMG distribution), create a gitignored local override:

```bash
cp Config/Signing.local.xcconfig.example Config/Signing.local.xcconfig
```

Fill in your Team ID:

```xcconfig
DEVELOPMENT_TEAM = YOUR_TEAM_ID
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Developer ID Application
OTHER_CODE_SIGN_FLAGS = --timestamp --options=runtime
```

`Config/Signing.xcconfig` includes this file via `#include? "Signing.local.xcconfig"` — the include is silently skipped when the file is absent.

<Warning>
Ad-hoc Release builds run locally but are not distributable. `tools/make-dmg.sh` requires `Config/Signing.local.xcconfig` and a Developer ID certificate in your Keychain.
</Warning>

## Build commands

<CodeGroup>

```bash title="Debug — run unit tests"
xcodegen generate
xcodebuild test \
  -project InkIt.xcodeproj \
  -scheme InkIt \
  -configuration Debug \
  -destination 'platform=macOS'
```

```bash title="Release — local installable build"
xcodegen generate
xcodebuild build \
  -project InkIt.xcodeproj \
  -scheme InkIt \
  -configuration Release \
  -derivedDataPath build
```

</CodeGroup>

The Release command writes the app bundle to `build/Build/Products/Release/InkIt.app`. CI uses separate derived-data paths (`build/ci-debug`, `build/ci-release`) but follows the same generate → test (Debug) → build (Release) sequence.

<Info>
If you build from the Xcode GUI without `-derivedDataPath build`, locate the product via **Product → Show Build Folder in Finder** instead of the `build/` path above.
</Info>

## Deep-sign Sparkle helpers

`project.yml` defines a **Deep-sign Sparkle helpers** post-build script on the InkIt target. After Xcode embeds Sparkle, nested helpers (`Downloader.xpc`, `Installer.xpc`, `Autoupdate`, `Updater.app`, and the framework itself) are re-signed inside-out so notarization accepts the bundle.

Behavior:

- **Skips** when `CODE_SIGNING_ALLOWED` is `NO` or Sparkle is not embedded.
- Uses `EXPANDED_CODE_SIGN_IDENTITY` from the active build — `-` for ad-hoc, Developer ID hash for maintainer builds.
- **Ad-hoc**: `codesign --force --sign -`
- **Developer ID**: `codesign --force --timestamp --options runtime --sign <identity>`

For ad-hoc local builds the script is effectively a no-op beyond sealing helpers. For notarized builds it is required — `tools/make-dmg.sh` fails fast if any Sparkle nested binary remains ad-hoc.

## Install to /Applications

After a Release build with `-derivedDataPath build`:

```bash
cp -R build/Build/Products/Release/InkIt.app /Applications/
```

<Check>
Replacing `/Applications/InkIt.app` is required for your build to take effect. Rebuilding alone does not update the installed copy if Xcode or Finder is still serving the old bundle.
</Check>

Quit any running InkIt instance before copying. If a release DMG version is already installed, the copy overwrites it — see [Runtime troubleshooting](/runtime-troubleshooting) if you encounter duplicate-instance or permission issues.

## Notarized distribution (maintainers)

Shipping a stapled `InkIt.dmg` is a separate workflow from local ad-hoc builds. `tools/make-dmg.sh`:

1. Requires `Config/Signing.local.xcconfig` and a `notarytool` Keychain profile (`inkit-notary`).
2. Runs `xcodegen generate` and a Release build to `build/`.
3. Verifies codesign (including Sparkle nested binaries).
4. Packages, signs, submits, and staples the DMG.

See [Release and distribution](/release-distribution) for the full publish pipeline (`make-appcast.sh`, `publish-release.sh`, Sparkle appcast).

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `InkIt.xcodeproj` missing after clone | Project is generated, not committed | Run `xcodegen generate` |
| `xcodebuild test` fails to launch host | Debug entitlements or wrong configuration | Build **Debug**; confirm `InkIt-Debug.entitlements` has `get-task-allow=true` |
| Notarization rejects Sparkle helpers | Nested binaries still ad-hoc | Confirm **Deep-sign Sparkle helpers** ran; check `project.yml` post-build script |
| `make-dmg.sh` fails on signing | No Developer ID override | Create `Config/Signing.local.xcconfig` from the example |
| Installed app unchanged after build | Old `/Applications/InkIt.app` still in place | `cp -R` the new `.app` over the installed copy |
| Scheme or target edits lost | Edited generated `.pbxproj` | Change `project.yml`, then `xcodegen generate` |

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites, entitlements overview, and the requirement to replace `/Applications/InkIt.app` after local builds.
</Card>
<Card title="Testing and CI" href="/testing-and-ci">
Run `xcodebuild test` locally and understand the CI workflow (design-token check, xcodegen, Debug test, Release build).
</Card>
<Card title="Release and distribution" href="/release-distribution">
Notarized DMG creation, Sparkle appcast, and GitHub release publishing for maintainers.
</Card>
<Card title="Quickstart" href="/quickstart">
First successful dictation after installing your build.
</Card>
</CardGroup>
