-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathproject.yml
More file actions
335 lines (323 loc) · 14.5 KB
/
Copy pathproject.yml
File metadata and controls
335 lines (323 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
name: Belay
# Everything derived from these three lives here and nowhere else, so the
# rename procedure in docs/NAMING.md stays a two-line change.
options:
bundleIdPrefix: com.perfectoweb
deploymentTarget:
macOS: "14.0"
createIntermediateGroups: true
groupSortPosition: top
settings:
base:
# PRODUCT_NAME belongs to the app target only — setting it project-wide also
# renames the test bundle's Swift module to Belay and the two collide.
ORG_IDENTIFIER: com.perfectoweb
MARKETING_VERSION: "1.8.1"
# Build 2. Build 1 was uploaded to App Store Connect and carried a Donate
# link in the sandboxed build, which is a guideline violation. A build
# number cannot be reused once uploaded, so the replacement needs its own.
CURRENT_PROJECT_VERSION: "20"
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
MACOSX_DEPLOYMENT_TARGET: "14.0"
ENABLE_USER_SCRIPT_SANDBOXING: YES
DEAD_CODE_STRIPPING: YES
CLANG_ENABLE_MODULE_DEBUGGING: NO
# Team ID read from the Developer ID Application certificate in the login
# keychain. Local builds still sign ad-hoc (CODE_SIGN_IDENTITY below);
# scripts/release.sh overrides the identity for a distributable build.
DEVELOPMENT_TEAM: VSY2EB4Y9E
CODE_SIGN_IDENTITY: "-"
CODE_SIGN_STYLE: Manual
packages:
BelayKit:
path: Packages/BelayKit
# Sparkle, in the direct channel only. It must never appear under Belay-MAS:
# Apple rejects third-party updaters, and scripts/verify-mas-build.sh fails
# the build if a Sparkle symbol, or SUFeedURL, shows up there.
Sparkle:
url: https://github.com/sparkle-project/Sparkle
from: "2.6.4"
# The two distribution channels are one target twice, not two codebases. Only
# the five ${...} values below differ; if a sixth ever appears, question it.
targetTemplates:
BelayAppChannel:
type: application
platform: macOS
sources:
- path: Sources/BelayApp
excludes:
- "Info*.plist" # generated per channel, not a resource
- path: Resources
buildPhase: resources
excludes:
- Entitlements # signing input, not a bundle resource
dependencies:
- package: BelayKit
product: BelayCore
- package: BelayKit
product: BelayPower
- package: BelayKit
product: BelaySettings
- package: BelayKit
product: BelayProviders
- package: BelayKit
product: BelayHookBridge
- package: BelayKit
product: BelaySupport
- package: BelayKit
product: BelayChannel
settings:
base:
PRODUCT_NAME: Belay
PRODUCT_BUNDLE_IDENTIFIER: com.perfectoweb.belay
INFOPLIST_KEY_NSHumanReadableCopyright: ""
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
COMBINE_HIDPI_IMAGES: YES
SWIFT_EMIT_LOC_STRINGS: YES
CODE_SIGN_ENTITLEMENTS: ${entitlements}
ENABLE_HARDENED_RUNTIME: ${hardenedRuntime}
# Xcode 15+ synthesises entitlements from the ENABLE_* settings and
# merges them over the file, so this has to agree with the file or the
# file quietly loses.
ENABLE_APP_SANDBOX: ${sandbox}
SWIFT_ACTIVE_COMPILATION_CONDITIONS: $(inherited) ${channel}
# ...and the same fact in a form BelayKit can read. Xcode does not pass
# SWIFT_ACTIVE_COMPILATION_CONDITIONS down to a local SwiftPM target
# (verified with a #warning probe), so `#if BELAY_MAS` works in the app
# and nowhere else. BelayChannel.DistributionChannel reads this instead.
BELAY_CHANNEL: ${channelName}
# One plist per channel, because SUFeedURL is exactly the thing the note
# here warned about: the App Store build must not carry it, and a shared
# plist cannot hold a key for one target and not the other. The path comes
# from the template attribute; "Info*.plist" is excluded from the sources
# above so neither target picks up the other's as a bundle resource.
info:
path: ${infoPlist}
properties:
CFBundleName: $(PRODUCT_NAME)
# Set here rather than left to Xcode's warning. The App Store requires
# a category and the direct build may as well agree with it.
LSApplicationCategoryType: public.app-category.developer-tools
CFBundleDisplayName: Belay
CFBundleShortVersionString: $(MARKETING_VERSION)
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundlePackageType: APPL
LSUIElement: true
LSMinimumSystemVersion: $(MACOSX_DEPLOYMENT_TARGET)
# Finder shows this in Get Info, so it is read by people and should be
# a copyright line. It used to carry "No data leaves your Mac", which is
# true of the App Store build and not of the direct one, where a daily
# update check does leave. One string cannot make a claim that depends
# on which channel it is compiled into, so it makes none.
NSHumanReadableCopyright: "© 2026 PerfectoWeb. Source available."
ITSAppUsesNonExemptEncryption: false
NSSupportsAutomaticTermination: false
NSSupportsSuddenTermination: false
BelayDistributionChannel: $(BELAY_CHANNEL)
targets:
# The privileged half of "hold through a closed lid": a root daemon that
# writes the kernel's SleepDisabled flag and clears it by itself when the
# app's heartbeat stops (docs/ROADMAP). Direct channel only — a sandboxed
# app cannot install a privileged helper, and guideline 2.4.5 covers both —
# so only the Belay target below embeds it, and verify-mas-build.sh fails
# the other build if it ever shows up there.
BelayLidHelper:
type: tool
platform: macOS
sources:
- path: Sources/BelayLidHelper
excludes:
- "*.plist" # embedded by the app target's copy phase, not compiled
- path: Sources/BelayApp/LidHold/LidHelperProtocol.swift
settings:
base:
PRODUCT_NAME: BelayLidHelper
PRODUCT_BUNDLE_IDENTIFIER: com.perfectoweb.belay.lidhelper
# SMAppService identifies the executable by its embedded Info.plist.
CREATE_INFOPLIST_SECTION_IN_BINARY: YES
INFOPLIST_FILE: Sources/BelayLidHelper/HelperInfo.plist
# Notarization refuses a bare-runtime executable, and the CI release
# of 1.3.0 died exactly there: the app template hardens the app, but
# a tool target hardens nothing unless told. The 1.3.0 DMG shipped
# because the manual pipeline re-signed with --options runtime.
ENABLE_HARDENED_RUNTIME: YES
# Never installed on its own: the app's copy phase is its only ride.
# In archive builds this sends the binary to UninstalledProducts,
# which that phase knows to look in.
SKIP_INSTALL: YES
# Direct channel: Developer ID, notarized, link-based tips, and Sparkle once
# there is a key to sign an appcast with. Ships first (docs/06).
Belay:
templates: [BelayAppChannel]
templateAttributes:
entitlements: Resources/Entitlements/Belay.entitlements
hardenedRuntime: "YES"
sandbox: "NO"
channel: BELAY_DIRECT
channelName: direct
infoPlist: Sources/BelayApp/Info-Direct.plist
dependencies:
- package: Sparkle
- target: BelayLidHelper
embed: false # laid out by the script below, not by Xcode's embed
# `SMAppService.daemon` expects the executable under Contents/MacOS and
# the launchd plist under Contents/Library/LaunchDaemons, a layout Xcode's
# embed phases cannot produce. Inputs and outputs are declared so the
# sandboxed script phase is allowed to write, and so it reruns only when
# they change.
postBuildScripts:
- name: Embed the lid helper
inputFiles:
- $(BUILT_PRODUCTS_DIR)/BelayLidHelper
- $(UNINSTALLED_PRODUCTS_DIR)/BelayLidHelper
- $(UNINSTALLED_PRODUCTS_DIR)/macosx/BelayLidHelper
- $(SRCROOT)/Sources/BelayLidHelper/com.perfectoweb.belay.lidhelper.plist
outputFiles:
- $(TARGET_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)/BelayLidHelper
- $(TARGET_BUILD_DIR)/$(CONTENTS_FOLDER_PATH)/Library/LaunchDaemons/com.perfectoweb.belay.lidhelper.plist
script: |
# In an ordinary build the helper is in BUILT_PRODUCTS_DIR; in an
# archive, SKIP_INSTALL sends a tool to UninstalledProducts instead.
# The 1.3.0 release archive was nearly cut without the helper because
# the first copy failed quietly and the phase's exit code was the
# last command's — hence set -e and an explicit existence check.
set -e
HELPER="$BUILT_PRODUCTS_DIR/BelayLidHelper"
[ -f "$HELPER" ] || HELPER="$UNINSTALLED_PRODUCTS_DIR/BelayLidHelper"
[ -f "$HELPER" ] || HELPER="$UNINSTALLED_PRODUCTS_DIR/macosx/BelayLidHelper"
[ -f "$HELPER" ] || { echo "error: BelayLidHelper not found in BUILT_PRODUCTS_DIR or UninstalledProducts" >&2; exit 1; }
cp "$HELPER" "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/BelayLidHelper"
mkdir -p "$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/Library/LaunchDaemons"
cp "$SRCROOT/Sources/BelayLidHelper/com.perfectoweb.belay.lidhelper.plist" \
"$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/Library/LaunchDaemons/"
info:
path: Sources/BelayApp/Info-Direct.plist
properties:
# Where Sparkle looks, and the key it checks every download against.
# The private half lives in the login Keychain of the release machine
# and nowhere else; this is the public half, and it is meant to be
# readable by anybody who opens the bundle.
SUFeedURL: https://perfectoweb.github.io/Belay/appcast.xml
SUPublicEDKey: Vzv5S2MFDz1si7eu25uDdjAYO/wDqwsA+Y2iDU0c3e8=
# Checked on a schedule, never installed without being asked. People
# running a system utility choose when it restarts (docs/06).
# False, and not a default. Belay does its own daily check through
# ReleaseChecker, which the About pane describes as the app's only
# network access. Letting Sparkle schedule a second one would make that
# sentence untrue, and would be two habits to explain instead of one.
# Sparkle here is the installer, reached from the Update Now button.
SUEnableAutomaticChecks: false
SUAutomaticallyUpdate: false
# Mac App Store channel: sandboxed, StoreKit tips, no updater of our own.
Belay-MAS:
templates: [BelayAppChannel]
# Xcode manages this one's signing. The App Store needs a distribution
# certificate and a provisioning profile that only Apple can issue, and
# letting Xcode create them on demand is both the supported path and the
# one the neighbouring shipped app used. The direct channel stays manual
# and ad-hoc, which is what keeps local builds working with no account.
#
# verify-mas-build.sh passes CODE_SIGN_STYLE=Manual back on the command
# line, so the offline check does not start asking Apple for anything.
templateAttributes:
entitlements: Resources/Entitlements/Belay-MAS.entitlements
# Hardened Runtime is a Developer ID/notarization concept; the App Store
# applies its own runtime policy and the setting is ignored there.
hardenedRuntime: "NO"
sandbox: "YES"
channel: BELAY_MAS
channelName: appStore
infoPlist: Sources/BelayApp/Info-MAS.plist
settings:
base:
ENABLE_USER_SELECTED_FILES: readwrite
CODE_SIGN_STYLE: Automatic
# "Apple Development" is precisely what automatic signing builds with;
# Xcode substitutes the distribution identity when the archive is
# exported. Two other values were tried here first and both are worth
# remembering:
#
# "Apple Distribution" — Xcode calls this conflicting provisioning
# settings and will not build, because naming the export-time
# identity removes the choice automatic signing exists to make.
#
# "" — builds, and produces an archive that is ad-hoc linker-signed
# with no team and no entitlements embedded at all. It looks entirely
# normal in the Organizer, and it is not sandboxed, which is the one
# thing an App Store build has to be.
CODE_SIGN_IDENTITY: "Apple Development"
configs:
Debug:
# Both channels write Belay.app into the same products directory, so a
# debug MAS build would silently replace the direct one and every
# local check after it would be inspecting the wrong bundle. Release —
# the configuration that gets archived and submitted — is plain Belay.
#
# The sandbox test action is pinned to Debug for the same reason: its
# TEST_HOST is generated from PRODUCT_NAME, so a Release test run would
# look for a Belay-MAS.app that Release does not build.
PRODUCT_NAME: Belay-MAS
# The only tests that run inside a sandbox.
#
# There used to be a comment here saying a test target on the MAS channel
# would "test the sandbox container, not the app". That is exactly the thing
# worth testing: `swift test` is not sandboxed, `BelayAppTests` is hosted by
# the direct build which is not sandboxed either, and both were green for a
# build in which the App Store app could not read `~/.claude` at all.
BelaySandboxTests:
type: bundle.unit-test
platform: macOS
sources:
- path: Tests/BelaySandboxTests
dependencies:
- target: Belay-MAS
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.perfectoweb.belay.sandboxtests
GENERATE_INFOPLIST_FILE: YES
# App-level tests only. The module suites live in the SwiftPM package and run
# via `swift test` — Xcode schemes generated from a spec cannot reference a
# local package's test targets. scripts/test.sh runs both.
BelayAppTests:
type: bundle.unit-test
platform: macOS
sources:
- path: Tests/BelayAppTests
dependencies:
- target: Belay
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.perfectoweb.belay.tests
GENERATE_INFOPLIST_FILE: YES
schemes:
Belay:
build:
targets:
Belay: all
BelayAppTests: [test]
run:
config: Debug
test:
config: Debug
targets:
- BelayAppTests
profile:
config: Release
archive:
config: Release
Belay-MAS:
build:
targets:
Belay-MAS: all
BelaySandboxTests: [test]
run:
config: Debug
test:
config: Debug
targets:
- BelaySandboxTests
profile:
config: Release
archive:
config: Release