Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
222 lines (184 loc) · 10.8 KB
/
Copy pathMakefile
File metadata and controls
222 lines (184 loc) · 10.8 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
# HanReader — MIT licensed. See LICENSE.
#
# The front door. `make run` is the one command a stranger with nothing but
# Xcode needs: it fetches its own pinned tooling, generates the Xcode project,
# builds, and launches.
#
# If you would rather not use make at all, `open Package.swift` builds every
# library target and runs the whole test suite with no additional tooling. make
# is only needed to produce a runnable application bundle.
SHELL := /usr/bin/env bash
.SHELLFLAGS := -euo pipefail -c
.DEFAULT_GOAL := help
TOOLS := .tools/bin
XCODEGEN := $(TOOLS)/xcodegen
SWIFTLINT := $(TOOLS)/swiftlint
SWIFTFORMAT := $(TOOLS)/swiftformat
# Single stamp written by bootstrap.sh on success. Targets depend on this rather
# than on the tool binaries for two reasons: bootstrap deliberately leaves a
# skipped tool's mtime alone, so binary-based prerequisites re-trigger it
# forever once tools.lock is touched by a checkout; and one shared prerequisite
# means `make -j` cannot run two concurrent bootstraps over the same files.
TOOLS_STAMP := .tools/.bootstrap-stamp
PROJECT := HanReader.xcodeproj
BUILD_DIR := .build/xcode
IOS_DEVICE ?= iPhone 17
# Keep in step with the deployment targets in Package.swift.
MACOS_DEST := platform=macOS
IOS_DEST := platform=iOS Simulator,name=$(IOS_DEVICE)
.PHONY: help bootstrap generate open run run-ios test test-all test-macos test-ios \
lint format format-check dict dict-check dict-licence dict-force clean distclean doctor
help: ## Show this help
@printf 'HanReader\n\n'
@printf 'Common targets:\n'
@grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \
| awk -F':.*?## ' '{printf " \033[1m%-14s\033[0m %s\n", $$1, $$2}'
@printf '\nFirst time here? make run\n'
# ── Tooling ──────────────────────────────────────────────────────────────────
bootstrap: ## Install pinned developer tooling into .tools/
@./Scripts/bootstrap.sh
$(TOOLS_STAMP): Scripts/tools.lock
@./Scripts/bootstrap.sh
doctor: ## Report toolchain and environment status
@printf 'Swift: %s\n' "$$(swift --version 2>&1 | awk 'NR==1')"
@printf 'Xcode: %s (pinned for CI: %s)\n' \
"$$(xcodebuild -version 2>/dev/null | awk 'NR==1 {print $$2}')" \
"$$(tr -d '[:space:]' < .xcode-version)"
@# `grep -c` exits 1 on a zero count, so swallow the status rather than
@# letting a fallback append a second number.
@printf 'iOS sims: %s runtime(s)\n' \
"$$(xcrun simctl list runtimes 2>/dev/null | grep -c '^iOS ' || true)"
@printf 'Tools: %s\n' "$$([ -d .tools/bin ] && ls .tools/bin | tr '\n' ' ' || echo 'not installed — run make bootstrap')"
# ── Dictionary pipeline ──────────────────────────────────────────────────────
CEDICT_SOURCE := Dictionaries/cc-cedict/cedict_ts.u8
CEDICT_OUTPUT := Resources/Generated/cedict.hanreaderdict
dict: $(CEDICT_OUTPUT) ## Compile the bundled dictionary into Resources/Generated
# Rebuilt when the pinned source changes or the compiler does. Compiling at
# build time rather than on first launch is what lets the app open a prepared
# database in under a millisecond instead of spending ~2.6s parsing text.
# A rule for the source itself, so a missing snapshot produces an instruction
# rather than make's "No rule to make target".
$(CEDICT_SOURCE):
@printf 'error: the pinned CC-CEDICT snapshot is missing:\n'
@printf ' %s\n\n' "$(CEDICT_SOURCE)"
@printf 'Fetch it with:\n ./Scripts/update-cedict.sh\n\n'
@printf 'It is pinned rather than downloaded at build time so that builds\n'
@printf 'are reproducible -- CC-CEDICT changes upstream almost daily.\n'
@exit 1
# Prerequisites cover everything that can change the output: the pinned source,
# the compiler's own sources, and the package manifests. A `find` alone is not
# enough -- a DELETED Swift file simply vanishes from the list, so make would
# see fewer prerequisites and consider an existing container up to date. The
# manifests are listed because a dependency or build-setting change alters the
# compiler without touching any .swift file.
CEDICT_DEPS := $(CEDICT_SOURCE) Package.swift Package.resolved \
$(shell find Sources/HanReaderCore Sources/HanReaderPersistence \
Sources/hanreader-dictgen -name '*.swift' 2>/dev/null)
$(CEDICT_OUTPUT): $(CEDICT_DEPS)
@mkdir -p Resources/Generated
@printf 'Compiling the bundled dictionary...\n'
@swift run -q -c release hanreader-dictgen cedict "$(CEDICT_SOURCE)" -o "$(CEDICT_OUTPUT)" --verbose
@swift run -q -c release hanreader-dictgen verify "$(CEDICT_OUTPUT)"
dict-check: ## Report whether the pinned CC-CEDICT snapshot is out of date
@./Scripts/update-cedict.sh --check
dict-licence: ## Check that every licence statement matches the pinned data
@./Scripts/check-dictionary-licence.sh
# Forces a rebuild regardless of timestamps, for when a prerequisite is missed.
dict-force: ## Recompile the bundled dictionary unconditionally
@rm -f $(CEDICT_OUTPUT)
@$(MAKE) dict
# ── Xcode project ────────────────────────────────────────────────────────────
generate: $(TOOLS_STAMP) $(CEDICT_OUTPUT) ## Regenerate the Xcode project from project.yml
@# One shell block on purpose. Make runs each recipe line in its own shell,
@# so an early `exit 0` in a guard would end only that line and the
@# following commands would still run.
@if [ ! -f project.yml ]; then \
printf 'project.yml does not exist yet — the app targets arrive in milestone M1 PR 3.\n'; \
else \
$(XCODEGEN) generate --quiet; \
mkdir -p $(PROJECT)/project.xcworkspace/xcshareddata/swiftpm; \
cp Package.resolved $(PROJECT)/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; \
printf 'Generated %s\n' "$(PROJECT)"; \
fi
@# The copy above matters: xcodebuild reads Package.resolved from inside the
@# generated project, not from the repository root. Without it the committed
@# pins are ignored and every build silently re-resolves dependencies.
open: generate ## Generate the project and open it in Xcode
@if [ -d $(PROJECT) ]; then open $(PROJECT); else open Package.swift; fi
# ── Build and run ────────────────────────────────────────────────────────────
run: generate ## Build and launch the macOS app
@if [ ! -d $(PROJECT) ]; then \
printf 'No app target yet. Until milestone M1 PR 3, use: make test\n'; \
exit 1; \
fi
@xcodebuild build \
-project $(PROJECT) -scheme HanReader-macOS -configuration Debug \
-destination '$(MACOS_DEST)' -derivedDataPath $(BUILD_DIR) -quiet
@open "$(BUILD_DIR)/Build/Products/Debug/HanReader.app"
run-ios: generate ## Build and launch the app in the iOS Simulator
@# Captured before grepping. Piping into `grep -q` lets it close the pipe on
@# the first match, simctl takes SIGPIPE, and under pipefail the probe reports
@# failure -- telling a machine that HAS a runtime that it has none.
@runtimes="$$(xcrun simctl list runtimes 2>/dev/null || true)"; \
if ! printf '%s\n' "$$runtimes" | grep -q '^iOS '; then \
printf 'error: no iOS simulator runtime installed.\n'; \
printf ' Install one with: xcodebuild -downloadPlatform iOS (~10 GB)\n'; \
exit 1; \
fi
@if [ ! -d $(PROJECT) ]; then \
printf 'No app target yet. Until milestone M1 PR 3, use: make test\n'; \
exit 1; \
fi
@xcodebuild build \
-project $(PROJECT) -scheme HanReader-iOS -configuration Debug \
-destination '$(IOS_DEST)' -derivedDataPath $(BUILD_DIR) -quiet
@# The bundle identifier is read back from the built app rather than
@# hard-coded: HANREADER_BUNDLE_ID_PREFIX is documented as overridable in
@# Local.xcconfig, so a literal org.openipc.hanreader would install one app
@# and then launch a different one.
@# Target the requested device by UDID throughout. `simctl install booted`
@# would install into whichever simulator happens to be booted -- a different
@# device than the one just built for, or none at all.
@udid="$$(xcrun simctl list devices available -j \
| python3 -c "import json,sys; d=json.load(sys.stdin)['devices']; \
print(next((x['udid'] for v in d.values() for x in v if x['name']=='$(IOS_DEVICE)'), ''))")"; \
if [ -z "$$udid" ]; then \
printf 'error: no available simulator named %s\n' "$(IOS_DEVICE)"; \
printf ' Available:\n'; xcrun simctl list devices available | grep -E '^ ' | head -12; \
printf ' Override with: make run-ios IOS_DEVICE="iPhone 16"\n'; \
exit 1; \
fi; \
xcrun simctl bootstatus "$$udid" -b >/dev/null 2>&1 || xcrun simctl boot "$$udid"; \
open -a Simulator --args -CurrentDeviceUDID "$$udid"; \
app="$(BUILD_DIR)/Build/Products/Debug-iphonesimulator/HanReader.app"; \
xcrun simctl install "$$udid" "$$app"; \
bundle_id="$$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$$app/Info.plist")"; \
xcrun simctl launch "$$udid" "$$bundle_id"
# ── Tests ────────────────────────────────────────────────────────────────────
test: ## Run the Swift package tests (fast; no simulator needed)
@swift build --build-tests -Xswiftc -warnings-as-errors
@swift test --skip-build
test-macos: generate
@xcodebuild test -project $(PROJECT) -scheme HanReader-macOS \
-destination '$(MACOS_DEST)' -derivedDataPath $(BUILD_DIR) -quiet
test-ios: generate
@xcodebuild test -project $(PROJECT) -scheme HanReader-iOS \
-destination '$(IOS_DEST)' -derivedDataPath $(BUILD_DIR) -quiet
test-all: test ## Run package tests plus both application test suites
@if [ -f project.yml ]; then $(MAKE) test-macos test-ios; \
else printf 'App targets arrive in milestone M1 PR 3; ran package tests only.\n'; fi
# ── Lint and format ──────────────────────────────────────────────────────────
lint: $(TOOLS_STAMP) ## Check formatting and lint rules
@$(SWIFTFORMAT) --lint .
@$(SWIFTLINT) lint --strict --quiet
format: $(TOOLS_STAMP) ## Apply formatting
@$(SWIFTFORMAT) .
format-check: $(TOOLS_STAMP)
@$(SWIFTFORMAT) --lint .
# ── Housekeeping ─────────────────────────────────────────────────────────────
clean: ## Remove build products
@rm -rf .build $(PROJECT) Resources/Generated/*.hanreaderdict
@printf 'Cleaned build products.\n'
distclean: clean ## Also remove the downloaded tooling
@rm -rf .tools
@printf 'Removed .tools — the next make target will re-bootstrap.\n'