-
-
Notifications
You must be signed in to change notification settings - Fork 119
Expand file tree
/
Copy pathMakefile
More file actions
602 lines (500 loc) · 24.5 KB
/
Copy pathMakefile
File metadata and controls
602 lines (500 loc) · 24.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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# Keyed on whether fvm is installed, not on the OS. Selecting by OS meant a
# Linux dev with fvm ran `make` against the ambient SDK while running
# `fvm flutter` by hand, and the two SDKs alternately rewrote the same
# .dart_tool/hooks_runner cache — every switch failed the next build hook with
# "Can't load Kernel binary: Invalid SDK hash". Jobs that install Flutter
# without fvm still take the bare-command branch; the ones that do install it
# (manual.yml, manual-capture-check.yml) already run `fvm flutter` themselves
# before calling make, so make now agrees with them instead of quietly using
# a second SDK alongside.
HAS_FVM := $(shell command -v fvm 2>/dev/null)
FLUTTER_CMD :=
DART_CMD :=
ifneq ($(HAS_FVM),)
FLUTTER_CMD := fvm flutter
DART_CMD := fvm dart
else
FLUTTER_CMD := flutter
DART_CMD := dart
endif
IOS_ARCHIVE_PATH = ./build/ios/archive/Runner.xcarchive
IOS_EXPORT_PATH = ./build/ios/export
MACOS_ARCHIVE_PATH = ./build/macos/archive/Runner.xcarchive
MACOS_EXPORT_PATH = ./build/macos/export
LOTTI_VERSION := $(shell yq '.version' pubspec.yaml | tr -d '"')
THRESH ?= 1000
MANUAL_VERSION ?= development
MANUAL_LOCALES ?= en de fr it es cs nl ro pt da sv
# Manual screenshots are staged and materialized locally (gitignored), then
# published to the R2 bucket by CI. No screenshot belongs in a git repository.
MANUAL_CAPTURE_DIR ?= $(abspath build/manual_capture)/$(MANUAL_VERSION)
MANUAL_MEDIA_DIR ?= $(abspath build/manual_media)
PEOPLE_INVENTORY_DIR ?= $(abspath build/people_inventory)
.PHONY: test
test:
$(DART_CMD) run tool/ci/run_tests.dart --coverage --exclude-tags "performance || eval-live"
.PHONY: test_standard
test_standard:
rm -rf coverage
$(DART_CMD) run tool/ci/run_tests.dart --coverage --exclude-tags "glados || performance || eval-live"
.PHONY: test_glados
test_glados:
rm -rf coverage
$(DART_CMD) run tool/ci/run_tests.dart --coverage --tags glados --exclude-tags eval-live
.PHONY: test_performance test_policy_check
test_performance:
$(DART_CMD) run tool/ci/run_tests.dart --tags performance --exclude-tags eval-live
test_policy_check:
$(DART_CMD) run tool/ci/check_test_tags.dart
.PHONY: analyze
analyze:
FLUTTER="$(FLUTTER_CMD)" DART="$(DART_CMD)" ./tool/analyze.sh
# Checks the OKF knowledge bundle in knowledge/: frontmatter conformance and,
# more importantly, that every code path a concept points at still exists.
# Checks that icons in lib/ come from the design system's semantic token layer
# (`LottiIcons`) instead of being picked glyph-by-glyph from Material at the call
# site. Ratcheted rather than absolute: the migration off Material spans many
# pull requests, and a check that is expected to fail is a check everyone learns
# to ignore. A file's legacy count may shrink or vanish, never grow. Reaching
# past the tokens to `LucideIcons` is not ratcheted — that one is always an error.
.PHONY: icon_check
icon_check:
$(DART_CMD) run tool/icons/validate.dart
# Checks the unreleased release notes in changelog.d/ — one new file per pull
# request instead of an edit to the top of CHANGELOG.md, which is what used to
# leave every open PR conflicted the moment one of them merged. Also fails when
# pubspec.yaml, CHANGELOG.md and the Flathub metainfo disagree about the
# released version: a metainfo left a version behind is invisible here and very
# visible to everyone on Flathub.
.PHONY: changelog_check
changelog_check:
$(DART_CMD) run tool/changelog/validate.dart
# Backfills the ThumbHash map for the demo-media catalog
# (lib/features/demo/media/generated/demo_media_thumb_hashes.g.dart): one
# blurred stand-in per R2 object, computed from the object's bytes. Idempotent —
# an object already in the map is skipped; FORCE=1 recomputes every object.
.PHONY: demo_media_thumb_hashes
demo_media_thumb_hashes:
$(DART_CMD) run tool/demo_media/thumb_hashes.dart $(if $(FORCE),--force,)
.PHONY: okf_check
okf_check:
$(DART_CMD) run tool/okf/validate.dart knowledge
# Parses every mermaid diagram under knowledge/ and docs/ with mermaid itself.
# Separate from okf_check because there is no Dart mermaid parser — a diagram that
# renders as an error box is invisible to the Dart validator, and three shipped
# that way. Everything a reader might open is gated, because every tree left
# outside this accumulated broken diagrams while it watched knowledge/ alone:
# two in the ADRs, twelve across docs/.
.PHONY: mermaid_check
mermaid_check:
cd tool/okf && npm ci --silent && npm test --silent && npm run --silent check
# The one target to run after touching knowledge/. Two separate targets meant the
# mermaid half got skipped, which is how unrenderable diagrams reached main.
.PHONY: knowledge_check
knowledge_check: okf_check mermaid_check
.PHONY: junit_test
junit_test:
$(FLUTTER_CMD) test --coverage --reporter json > TEST-report.jsonl
.PHONY: slow_tests
slow_tests: deps
@mkdir -p reports
$(DART_CMD) run tool/ci/run_tests.dart --exclude-tags "performance || eval-live" --file-reporter json:reports/tests.json
$(DART_CMD) run test/tool/analyze_test_timings.dart reports/tests.json $(THRESH)
.PHONY: junit_upload
junit_upload:
$(DART_CMD) pub global activate junitreport
$(DART_CMD) pub global run junitreport:tojunit --input TEST-report.jsonl --output junit.xml
./.buildkite/junit_upload.sh
.PHONY: integration_test
integration_test:
$(FLUTTER_CMD) test integration_test --exclude-tags tutorial-video --exclude-tags store-screenshots
.PHONY: clean
clean:
$(FLUTTER_CMD) clean
.PHONY: deps
deps:
$(FLUTTER_CMD) pub get
.PHONY: check_flatpak_foreign_deps
check_flatpak_foreign_deps: deps
python3 flatpak/check_foreign_deps.py
.PHONY: sort_arb_files
sort_arb_files:
find lib/l10n/ -type f -name '*.arb' -exec $(DART_CMD) run arb_utils sort -i {} \;
.PHONY: l10n
l10n: deps
$(FLUTTER_CMD) gen-l10n
@echo "Missing translations:"
@cat missing_translations.txt
.PHONY: doctor
doctor:
$(FLUTTER_CMD) doctor
.PHONY: coverage_report
coverage_report:
lcov --remove coverage/lcov.info 'lib/classes/*.g.dart' 'lib/database/*.g.dart' 'lib/l10n/*.dart' -o coverage/new_lcov.info
genhtml coverage/new_lcov.info -o coverage --no-function-coverage
open coverage/index.html
.PHONY: coverage
coverage: test coverage_report
.PHONY: coverage_standard
coverage_standard: test_standard coverage_report
.PHONY: coverage_glados
coverage_glados: test_glados coverage_report
.PHONY: build_runner
build_runner: deps
$(DART_CMD) run build_runner build --delete-conflicting-outputs
.PHONY: design_system_import
design_system_import: deps
$(DART_CMD) run tool/design_system/generate_tokens.dart
$(DART_CMD) format lib/features/design_system/theme/generated/design_tokens.g.dart
.PHONY: watch
watch: l10n
$(DART_CMD) run build_runner watch --delete-conflicting-outputs
.PHONY: activate_fluttium
activate_fluttium:
$(FLUTTER_CMD) pub global activate fluttium_cli
.PHONY: fluttium_linux
fluttium_linux:
fluttium test test_flows/habit_flow.yaml --reporter expanded
.PHONY: fluttium_production
fluttium_production:
fluttium test test_flows/habit_flow.yaml --flavor production --target lib/main.dart
.PHONY: fluttium_docs
fluttium_docs: manual_screenshots
@echo "fluttium_docs is deprecated; generated manual media is ready in $(MANUAL_MEDIA_DIR)."
# Play Store listing screenshots, captured on an Android emulator through
# `flutter drive` so they are what a phone actually renders. Boots $(LOTTI_AVD)
# when no device is attached; see tool/store_screenshots/android.sh for the
# knobs. Output lands in build/store_screenshots/android (gitignored).
.PHONY: store_screenshots_android
store_screenshots_android:
FLUTTER="$(FLUTTER_CMD)" ./tool/store_screenshots/android.sh
# App Store listing screenshots, captured on iOS simulators the same way. The
# simulators render at their native device sizes, which are exactly the sizes
# App Store Connect asks for (6.9" iPhone, 13" iPad); see
# tool/store_screenshots/ios.sh for the knobs. Output lands in
# build/store_screenshots/ios/<device> (gitignored).
.PHONY: store_screenshots_ios
store_screenshots_ios:
FLUTTER="$(FLUTTER_CMD)" ./tool/store_screenshots/ios.sh
.PHONY: manual_deps
manual_deps: docs-site/node_modules
# A real file target, not a phony one: the screenshot targets below each need
# these dependencies, and the eleven-locale loop would otherwise pay `npm ci`
# eleven times. Reinstalls only when the lockfile is newer than the tree.
docs-site/node_modules: docs-site/package-lock.json
npm --prefix docs-site ci
@touch $@
.PHONY: manual_start
manual_start:
npm --prefix docs-site start
.PHONY: manual_build
manual_build:
npm --prefix docs-site run build
.PHONY: manual_serve
manual_serve:
npm --prefix docs-site run serve
.PHONY: manual_check
manual_check:
npm --prefix docs-site run check
.PHONY: manual_check_media
manual_check_media:
npm --prefix docs-site run validate -- --media-root "$(MANUAL_MEDIA_DIR)" --version "$(MANUAL_VERSION)"
.PHONY: manual_screenshots
manual_screenshots: manual_deps
@set -e; for locale in $(MANUAL_LOCALES); do \
$(MAKE) manual_screenshots_shard MANUAL_LOCALE="$$locale"; \
done
$(MAKE) manual_screenshots_manifest
# One locale's slice of the catalog: capture its PNGs, then convert only that
# locale to WebP and leave the manifest to a later pass over the merged tree.
# CI fans these out across runners — capturing all $(words $(MANUAL_LOCALES))
# locales in sequence takes over two hours, more than a single job can spend.
.PHONY: manual_screenshots_shard
manual_screenshots_shard: docs-site/node_modules
$(MAKE) manual_screenshots_locale \
MANUAL_LOCALE="$(MANUAL_LOCALE)" \
MANUAL_CAPTURE_DIR="$(MANUAL_CAPTURE_DIR)/$(MANUAL_LOCALE)"
npm --prefix docs-site run manifest -- --capture-dir "$(MANUAL_CAPTURE_DIR)" --output-root "$(MANUAL_MEDIA_DIR)" --version "$(MANUAL_VERSION)" --locales "$(MANUAL_LOCALE)" --skip-manifest
# Write and validate the manifest over an already-materialized media tree.
# Every locale's WebP files must be present, whether one machine produced them
# or eleven did.
.PHONY: manual_screenshots_manifest
manual_screenshots_manifest: docs-site/node_modules
npm --prefix docs-site run manifest -- --output-root "$(MANUAL_MEDIA_DIR)" --version "$(MANUAL_VERSION)" --manifest-only
$(MAKE) manual_check_media MANUAL_VERSION="$(MANUAL_VERSION)" MANUAL_MEDIA_DIR="$(MANUAL_MEDIA_DIR)"
.PHONY: manual_screenshots_locale
manual_screenshots_locale:
mkdir -p "$(MANUAL_CAPTURE_DIR)"
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/settings/ui/settings_home_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/daily_os_next/ui/pages/day_planning_modal_screenshots_test.dart --name '^(mini|desktop) (capture captured|captured|reconcile) — (dark|light)$$'
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/daily_os_next/ui/pages/day_page_screenshots_test.dart --name '^((mini|desktop) (agenda|timeline)|(pro|desktop) timeline arrange mode) — (dark|light)$$'
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/daily_os_next/ui/pages/day_page_screenshots_test.dart --name '^manual daily OS'
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/daily_os_next/ui/pages/daily_os_settings_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/settings/ui/settings_definitions_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/pages/create/create_measurement_dialog_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/settings/ui/settings_preferences_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/tasks/ui/widgets/task_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/projects/ui/pages/projects_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/events/ui/pages/events_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/journal/ui/pages/journal_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/insights/ui/time_analysis_screenshots_test.dart --name 'manual'
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/habits/ui/habits_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/speech/ui/widgets/recording/audio_recording_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/settings/ui/settings_advanced_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/whats_new/ui/whats_new_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/onboarding/ui/onboarding_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/ai/ui/settings/ai_settings_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/agents/ui/agents_manual_screenshots_test.dart
LOTTI_MANUAL_LOCALE="$(MANUAL_LOCALE)" LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)" fvm flutter test test/features/sync/ui/sync_manual_screenshots_test.dart
.PHONY: manual_screenshots_macos
manual_screenshots_macos:
mkdir -p "$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/macos"
LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/macos" fvm flutter drive -d macos --driver=test_driver/manual_screenshots_driver.dart --target=integration_test/manual_screenshots_test.dart --dart-define=LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/macos"
.PHONY: manual_screenshots_linux
manual_screenshots_linux:
mkdir -p "$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/linux"
LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/linux" fvm flutter drive -d linux --driver=test_driver/manual_screenshots_driver.dart --target=integration_test/manual_screenshots_test.dart --dart-define=LOTTI_SCREENSHOT_DIR="$(MANUAL_CAPTURE_DIR)/legacy/${LOTTI_VERSION}/linux"
# The People (relationships) design-handover inventory: every People surface,
# each state once, at both viewports. Deliberately outside the localized manual
# catalog above — it registers no case IDs and publishes nothing. It exists so
# a design handover's screenshot bundle can be regenerated, and so a People
# redesign's "after" half comes from the same fixtures as its "before" half.
.PHONY: people_inventory_screenshots
people_inventory_screenshots:
mkdir -p "$(PEOPLE_INVENTORY_DIR)"
LOTTI_SCREENSHOT_DIR="$(PEOPLE_INVENTORY_DIR)" fvm flutter test test/features/relationships/ui/pages/people_inventory_screenshots_test.dart
@echo "People inventory written to $(PEOPLE_INVENTORY_DIR)"
.PHONY: bundle
bundle:
$(FLUTTER_CMD) build bundle
.PHONY: widgetbook_macos_build
widgetbook_macos_build:
bash tool/widgetbook/build_macos_bundle.sh
.PHONY: widgetbook_macos_upload
widgetbook_macos_upload:
bash tool/widgetbook/build_macos_bundle.sh --skip-build --upload-release
.PHONY: widgetbook_macos_publish
widgetbook_macos_publish:
bash tool/widgetbook/build_macos_bundle.sh --upload-release
#######################################
.PHONY: ios_build_ipa
ios_build_ipa:
$(FLUTTER_CMD) build ipa
.PHONY: ios_build
ios_build: clean_test ios_build_ipa
.PHONY: ios_fastlane_beta
ios_fastlane_beta:
cd ios && fastlane beta && cd ..
.PHONY: ios_fastlane_build
ios_fastlane_build:
cd ios && fastlane do_build && cd ..
.PHONY: ios_fastlane_upload
ios_fastlane_upload:
cd ios && fastlane do_upload && cd ..
.PHONY: ios_fastlane_match
ios_fastlane_match:
cd ios && fastlane match --generate_apple_certs false && cd ..
.PHONY: ios_open
ios_open:
open $(IOS_ARCHIVE_PATH)
.PHONY: ipa
ipa: ios_build
xcodebuild -exportArchive -archivePath $(IOS_ARCHIVE_PATH) \
-exportOptionsPlist ./ios/Runner/exportOptions.plist \
-exportPath $(IOS_EXPORT_PATH) \
-allowProvisioningUpdates
.PHONY: ios_upload
ios_upload:
@xcrun altool --upload-app --type ios -f $(IOS_EXPORT_PATH)/Lotti.ipa \
-u $(APPLEID) -p $(LOTTI_APPSTORE_CONNECT)
.PHONY: ios
ios: ios_build ios_fastlane_build ios_fastlane_upload
.PHONY: macos_build_flutter
macos_build_flutter:
$(FLUTTER_CMD) build macos
.PHONY: releases
releases: android_build_release macos_build_flutter ios_build_ipa
.PHONY: macos_build
macos_build: clean_test macos_build_flutter
.PHONY: macos_archive
macos_archive:
xcodebuild -workspace ./macos/Runner.xcworkspace \
-config Release -scheme Runner \
-archivePath $(MACOS_ARCHIVE_PATH) archive
.PHONY: macos_pkg
macos_pkg:
xcodebuild -exportArchive -archivePath $(MACOS_ARCHIVE_PATH) \
-exportOptionsPlist ./macos/Runner/exportOptions.plist \
-exportPath $(MACOS_EXPORT_PATH) \
-allowProvisioningUpdates
.PHONY: macos_upload
macos_upload:
@xcrun altool --upload-app --type macos -f $(MACOS_EXPORT_PATH)/Lotti.pkg \
-u $(APPLEID) -p $(LOTTI_APPSTORE_CONNECT)
.PHONY: macos_open
macos_open: macos_build macos_archive
open $(MACOS_ARCHIVE_PATH)
.PHONY: macos_fastlane_build
macos_fastlane_build:
cd macos && fastlane do_build && cd ..
.PHONY: macos_fastlane_upload
macos_fastlane_upload:
cd macos && fastlane do_upload && cd ..
.PHONY: macos_fastlane_match
macos_fastlane_match:
cd macos && fastlane match --generate_apple_certs false && cd ..
.PHONY: macos_fastlane_export
macos_fastlane_export:
cd macos && fastlane do_package && cd ..
.PHONY: macos
macos: macos_build macos_fastlane_build macos_fastlane_upload macos_fastlane_export
.PHONY: macos_export
macos_export: macos_build macos_fastlane_export
.PHONY: macos_testflight_cli
macos_testflight_cli: macos_build macos_archive macos_pkg macos_upload
.PHONY: macos_cli
macos_cli: macos_build macos_archive macos_pkg macos_upload
.PHONY: macos_local
macos_local: macos_build
open ./build/macos/Build/Products/Release/
.PHONY: android_build
android_build:
$(FLUTTER_CMD) build appbundle
.PHONY: android_build_release
android_build_release:
$(FLUTTER_CMD) build appbundle --release
.PHONY: linux_build
linux_build:
$(FLUTTER_CMD) build linux
.PHONY: linux
linux: l10n test linux_build
.PHONY: windows
windows: clean_test
$(FLUTTER_CMD) build windows
.PHONY: tag_push
tag_push:
git tag ${LOTTI_VERSION}
git push origin ${LOTTI_VERSION}
# Google Play store-track releases. Each pushes a play/<track>/<version> tag,
# and .github/workflows/play-promote.yml promotes the build that the release
# tag already put on the internal track - no rebuild, Play refuses a version
# code it has seen. Closed testing is Play's `alpha` track; open testing is
# `beta` (tag play/beta/<version> by hand if ever wanted); production is
# `production`. Run from the commit whose pubspec carries the version to
# promote, because the workflow refuses a tag that disagrees with it.
#
# A canned recipe rather than a shared prerequisite: make builds a
# prerequisite once per invocation, so `make android_closed_testing
# android_release` would have pushed the first tag only and reported both
# targets done.
define play_tag_push
@test -n "$(LOTTI_VERSION)" || { echo "LOTTI_VERSION is empty: install yq (brew install yq)" >&2; exit 1; }
git tag play/$(1)/$(LOTTI_VERSION)
git push origin play/$(1)/$(LOTTI_VERSION)
endef
.PHONY: android_closed_testing
android_closed_testing:
$(call play_tag_push,alpha)
.PHONY: android_release
android_release:
$(call play_tag_push,production)
.PHONY: all
all: ios macos
.PHONY: splash
splash:
$(DART_CMD) run flutter_native_splash:create
.PHONY: icons
icons:
dart run flutter_launcher_icons:main
.PHONY: clean_test
clean_test: clean deps build_runner l10n test
# Capture a file descriptor diagnostic snapshot of the running Lotti process.
# Writes to tmp/fd/fd-<timestamp>.txt and prints to stdout. Compare snapshots
# over time to spot monotonic FD growth (a leak) vs a stable working set.
# Override the PID if auto-detection picks the wrong process:
# make fd_snapshot PID=12345
.PHONY: fd_snapshot
fd_snapshot:
@mkdir -p tmp/fd
@PID_VAL="$${PID:-}"; \
if [ -z "$$PID_VAL" ]; then PID_VAL=$$(pgrep -x Lotti 2>/dev/null | head -n1); fi; \
if [ -z "$$PID_VAL" ]; then PID_VAL=$$(pgrep -x lotti 2>/dev/null | head -n1); fi; \
if [ -z "$$PID_VAL" ]; then \
echo "No running Lotti process found."; \
echo "Launch Lotti, or pass an explicit PID: make fd_snapshot PID=<pid>"; \
exit 1; \
fi; \
TS=$$(date +%Y-%m-%d-%H%M%S); \
OUT="tmp/fd/fd-$$TS.txt"; \
{ \
echo "=== Lotti FD snapshot $$TS pid=$$PID_VAL ==="; \
if command -v launchctl >/dev/null 2>&1; then \
echo "--- launchctl limit maxfiles ---"; \
launchctl limit maxfiles 2>/dev/null || true; \
fi; \
if command -v sysctl >/dev/null 2>&1; then \
echo "--- sysctl ---"; \
sysctl kern.maxfiles kern.maxfilesperproc 2>/dev/null || true; \
fi; \
if [ -r "/proc/$$PID_VAL/limits" ]; then \
echo "--- /proc/$$PID_VAL/limits (Max open files) ---"; \
grep "Max open files" "/proc/$$PID_VAL/limits" || true; \
fi; \
LSOF_TMP=$$(mktemp -t lotti-fd-snapshot 2>/dev/null || mktemp); \
lsof -p "$$PID_VAL" >"$$LSOF_TMP" 2>/dev/null || true; \
echo "--- real FD count (excludes cwd/rtd/txt/mem) ---"; \
awk 'NR>1 && $$4 ~ /^[0-9]+[A-Za-z]*$$/' "$$LSOF_TMP" | wc -l | awk '{print "total_real_fds="$$1}'; \
echo "--- grouped by type ---"; \
awk 'NR>1 && $$4 ~ /^[0-9]+[A-Za-z]*$$/ {print $$5}' "$$LSOF_TMP" | sort | uniq -c | sort -rn; \
rm -f "$$LSOF_TMP"; \
} | tee "$$OUT"; \
echo ""; \
echo "Saved to $$OUT"
# --- Tutorial videos (tools/tutorial_videos) --------------------------------
# Full pipeline for one locale: TTS pre-pass -> real app under Xvfb driven by
# flutter drive (virtual mic + screen capture) -> OpenMontage composition.
# Requires: .env with GEMINI_API_KEY/MELIOUS_API_KEY/MELIOUS_BASE_URL, and a
# pinned ../OpenMontage checkout (see tools/tutorial_videos/config/openmontage.pin).
TUTORIAL_SCENARIO ?= create_task_from_audio
TUTORIAL_LOCALE ?= en
TUTORIAL_LOCALES ?= en de
# desktop (1920x1080, unsuffixed output — the original variant) or mobile
# (phone-shaped window under the app's 960px desktop breakpoint, `_mobile`
# suffixed output). See tools/tutorial_videos/tutorial_videos/__main__.py's
# DEVICE_SIZES.
TUTORIAL_DEVICE ?= desktop
.PHONY: tutorial_video
tutorial_video:
cd tools/tutorial_videos && python3 -m tutorial_videos build \
--scenario $(TUTORIAL_SCENARIO) --locale $(TUTORIAL_LOCALE) \
--device $(TUTORIAL_DEVICE)
.PHONY: tutorial_videos_all
tutorial_videos_all:
for locale in $(TUTORIAL_LOCALES); do \
$(MAKE) tutorial_video TUTORIAL_LOCALE=$$locale || exit 1; \
done
# Uploads an already-built MP4 to Cloudflare R2 and prints its public URL.
# Requires .env with R2_ACCOUNT_ID/R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY/
# R2_BUCKET_NAME/R2_PUBLIC_BASE_URL, and `pip install boto3`.
.PHONY: tutorial_video_publish
tutorial_video_publish:
cd tools/tutorial_videos && python3 -m tutorial_videos publish \
--scenario $(TUTORIAL_SCENARIO) --locale $(TUTORIAL_LOCALE) \
--device $(TUTORIAL_DEVICE)
# Uploads external before/after PNGs to an immutable, commit-addressed R2
# prefix. Requires the same R2_* .env values and boto3 as tutorial publishing.
PR_SCREENSHOT_PYTHON ?= python3
PR_SCREENSHOT_SOURCE ?=
PR_SCREENSHOT_TOPIC ?=
PR_SCREENSHOT_COMMIT ?= $(shell git rev-parse HEAD)
PR_SCREENSHOT_ENV ?= .env
PR_SCREENSHOT_ARGS = --source "$(PR_SCREENSHOT_SOURCE)" \
--topic "$(PR_SCREENSHOT_TOPIC)" --commit "$(PR_SCREENSHOT_COMMIT)" \
--env-file "$(PR_SCREENSHOT_ENV)"
.PHONY: pr_screenshots_publish
pr_screenshots_publish:
@test -n "$(PR_SCREENSHOT_SOURCE)" || \
(echo "PR_SCREENSHOT_SOURCE is required" && exit 1)
@test -n "$(PR_SCREENSHOT_TOPIC)" || \
(echo "PR_SCREENSHOT_TOPIC is required" && exit 1)
$(PR_SCREENSHOT_PYTHON) tool/pr_screenshot_publish.py $(PR_SCREENSHOT_ARGS)