diff --git a/work/projects/vscode-migration/findings.md b/work/projects/vscode-migration/findings.md index 99d59969..c4df652d 100755 --- a/work/projects/vscode-migration/findings.md +++ b/work/projects/vscode-migration/findings.md @@ -2,6 +2,22 @@ Back to [[projects/vscode-migration/vscode-migration]]. Verified on Xcode 26.6 / Swift 6.3.3, Sep 2026. +## Browser package (Xcode 27.2, Oct 2026) + +- The project targets Xcode 27 now; Xcode 26 isn't a constraint any more. +- A package folder can overlap project groups: other targets keep compiling files inside `macOS/DuckDuckGo` by path, and the app target keeps resource file references there (verified with a small xcodegen project and the real app). +- Asset catalogs in a package: Xcode (26.6 and 27.2) and `swift build --build-system swiftbuild` (27.2) compile `Assets.car` and generate `ImageResource`/`ColorResource`, but **not** `NSImage.foo`/`NSColor.foo`/`Color.foo` extensions → ~575 call sites moved to `NSImage(resource:)` / `NSColor(resource:)` / `Color(.foo)` (found by building with `ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS=NO`). Native `swift build` handles neither. +- Core Data models with `codeGenerationType="class"` work in a package (Xcode and swiftbuild): entity classes are generated in the package module, `.momd` goes to the package resource bundle → load with `Bundle.module`. Don't keep a copy in the app target (duplicate `@objc` entity classes). +- Xcode treats only a configuration named `Debug` as debug for packages: `CI` builds packages as release (no `DEBUG`, no testability). `SPM_FORCE_DEBUG=1` (read by `Package.swift`) + `ENABLE_TESTABILITY=true` on the command line fix it; command-line build settings reach package targets, project-level settings don't. +- SwiftPM caches evaluated manifests by content in `~/Library/Caches/org.swift.swiftpm/manifests`, not by environment: after building without `SPM_FORCE_DEBUG`, a `CI` build reuses the manifest without `DEBUG` until `Package.swift` changes. Fresh CI runners are fine; locally edit `Package.swift` or clear the cache. +- Storyboard objects with `customModuleProvider="target"` resolve to the module of the target compiling the storyboard → after moving the code into a package, classes weren't found (crash in `NavigationBarViewController`). Moot now that the last storyboard is dropped. +- Transitive modules used only through extensions (e.g. Punycode's `String.idnaDecoded`) link in Debug but not in release configurations → declare them as explicit package dependencies. +- `DataBrokerProtection-macOS/Package.swift` had `.package(path: "../VPN")` (doesn't exist; Xcode resolves it by identity through the workspace) — breaks `swift build`; fixed to `../../../SharedPackages/VPN`. +- Building the host app without `-onlyUsePackageVersionsFromResolvedFile` re-resolved packages and rewrote the project `Package.resolved` (`xctest-dynamic-overlay` → `swift-issue-reporting`). +- VSCode host app: the app has system extension entitlements → the hardened runtime refuses `disable-library-validation` ("Hardened Runtime relaxation entitlements disallowed on System Extensions", process killed at exec). Instead `sign-dylib.sh` signs the `swift build` dylib and the frameworks it links with the host app's identity. +- `swift build --build-system swiftbuild --product DuckDuckGoBrowserDynamic` output: `.build/out/Products/Debug/libDuckDuckGoBrowserDynamic.dylib`; links GRDB, Sparkle, OpenSSL, Lottie frameworks next to it. At runtime the dylib finds its resource bundle in the host app (`Contents/Resources/DuckDuckGoBrowser_DuckDuckGo_Privacy_Browser.bundle`). +- Concurrency warnings ("error in the Swift 6 language mode") are the same in the old app target and in the package on Xcode 27 — existing debt, not caused by the move. + ## App target readiness - DMG and App Store targets compile identical sources (1398 files); `FEATURE_FLAGS` is empty for the app — only `DEBUG` remains (SPM handles it). diff --git a/work/projects/vscode-migration/vscode-migration.md b/work/projects/vscode-migration/vscode-migration.md index 90b9f91d..39890081 100755 --- a/work/projects/vscode-migration/vscode-migration.md +++ b/work/projects/vscode-migration/vscode-migration.md @@ -23,8 +23,10 @@ SPM-compatible, so `swift build` + SourceKit-LSP work, and configure | Drop `#if SPARKLE/APPSTORE` | ✅ Mar 2026 — DMG and App Store targets now compile the same 1398 files | | Drop dead target flags (`SANDBOX_TEST_TOOL`, `ALPHA`, `NETWORK_EXTENSION` in shared files) | ✅ [#6983](https://github.com/duckduckgo/apple-browsers/pull/6983) | | Replace sandbox-test-tool with App Store UI tests (`AppStoreSandboxUITests`, CI leg along full DMG runs) | ✅ [#6980](https://github.com/duckduckgo/apple-browsers/pull/6980) | -| Split app files shared with helper targets | 🔄 A+B in review ([#7029](https://github.com/duckduckgo/apple-browsers/pull/7029)), C ready — see [Target split stages](#target-split-stages) | -| Drop storyboards | 🔄 3 left on main: `Feedback`, `NavigationBar`, `PasswordManager` (`drop-sb-*` worktrees) | +| Split app files shared with helper targets | 🔄 A+B in review ([#7029](https://github.com/duckduckgo/apple-browsers/pull/7029)), C ready, G done; the rest stays shared by path — see [Target split stages](#target-split-stages) | +| Asset resource initializers instead of asset symbol extensions | ✅ committed, `alex/asset-resource-symbols` (needs a PR) | +| Drop storyboards | ✅ last one (`NavigationBar`) is `alex/drop-navigationbar-storyboard` (PR open); `Feedback` #6866, `PasswordManager` #6950 merged | +| **Browser code as a SwiftPM package** | 🔄 builds and runs from Xcode and VSCode — `alex/spm-browser-package`, see [Browser package](#browser-package) | | Core Data models in code | ❌ **Cancelled** 2026-10-01 — Xcode 27 builds these in SPM | | Asset catalogs for `swift build` | ❌ **Cancelled** 2026-10-01 — Xcode 27 builds these in SPM | | swift-build fork (Provisional Inputs) | ⏸ not started; may be unnecessary | @@ -39,11 +41,23 @@ SPM-compatible, so `swift build` + SourceKit-LSP work, and configure ## Plan (next steps) -1. **Extract files shared with helper targets into packages** — in progress, see [Target split stages](#target-split-stages). Started from 38 app files also compiled into VPN / PIR agent / NetP extensions. Dead flags are gone (#6983); unused memberships trimmed and stages A+B moved (#7029). The old draft `netp-dependencies` is superseded. -2. **Decide on strings & resources in the package** (tech design): 1964 `NSLocalizedString` without `bundle:` (→ `Bundle.main`), ~60 resource files, 8 `Bundle.main.url(…)`. `bundleIdentifier`/`appGroup` via `Bundle.main` can stay. -3. **App code as a library package** + thin app target (`AppMain`, Info.plist, entitlements, embed phases for login items / extensions). -4. **VSCode tasks with cached helper targets**: gate script hashes each extension's inputs (git-based, not mtime) and skips `xcodebuild` when unchanged; assemble step copies prebuilt extensions and signs. Only pays off once the app itself builds with `swift build`. See [[projects/vscode-migration/findings#Extensions in VSCode tasks]]. -5. Convert ~57 string-name image lookups (`NSImage(named:)`, `Image("…")`) to asset symbols — they break once code moves to `Bundle.module`. +1. **PRs in order:** #7029 (A+B) → C (`move-vpn-pixel-event`) / G (`move-helper-only-files`) → `alex/asset-resource-symbols` → `alex/drop-navigationbar-storyboard` → `alex/spm-browser-package`. +2. **Browser package PR follow-ups** — see [Browser package](#browser-package): localization export (`loc_export.sh`), CI checks for the VSCode setup, dropping the duplicated app-target package product dependencies. +3. **Helper-target split, later:** D, E1, E2+H, F aren't needed for the package (helpers keep compiling the shared files by path); do them when there's a reason (F with the KeyedStoring migration). +4. **VSCode tasks with cached helper targets** (extensions/login items built by Xcode only when their inputs change) — the host app already contains them; revisit if host rebuilds are too slow. See [[projects/vscode-migration/findings#Extensions in VSCode tasks]]. + +## Browser package + +Worktree `spm-browser-package`, branch `alex/spm-browser-package` (on top of `alex/move-helper-only-files` + `alex/asset-resource-symbols` + `alex/drop-navigationbar-storyboard`; package work uncommitted). + +- `macOS/DuckDuckGo/Package.swift`: target `DuckDuckGo_Privacy_Browser` (same module name as the former app target: storyboards, archived class names, `@testable import`) with `path: "."` — files stay in place. Products `DuckDuckGoBrowser` (static, linked into the app targets) and `DuckDuckGoBrowserDynamic` (for `swift build`). +- Resources stay in the app targets (`Bundle.main`: strings, JSON, scripts, app icons); asset catalogs (for generated `ImageResource`/`ColorResource`) and the 8 Core Data models (codegen entity classes) are processed by the package — models now load from `Bundle.module`. +- App targets: only `DuckDuckGoApp/main.swift` (`AppMain.main()`), module `DuckDuckGoApp`; package reference + `DuckDuckGoBrowser` product added, existing product dependencies kept. +- `SPM_FORCE_DEBUG` in `Package.swift` (same as AppKitExtensions/SyncUI): the CI configuration maps to release for packages. +- `REVIEW` compilation condition can't reach the package → Browser Tools Panel debug menu item is gated at runtime (`isDebugBuild || isReviewBuild`). +- VSCode/Cursor: open `macOS/DuckDuckGo`; `.vscode/` has tasks (host app → `swift build --build-system swiftbuild` → sign), `launch.json`, `make-host-app.sh`, `sign-dylib.sh`, `launcher/launcher.c`. +- Verified (Xcode 27.2, offline): all product schemes Debug, `macOS Browser` Review, unit + integration tests `build-for-testing` Debug and CI (with `SPM_FORCE_DEBUG=1`), Xcode-signed Debug app and the VSCode host run without crashes, `swift build` of the dylib. +- Open: `loc_export.sh` / string catalog extraction now that sources are in a package; CI doesn't build the VSCode path; `Package.resolved` in `macOS/DuckDuckGo` (copied from the project) — keep in sync or generate. ## Target split stages @@ -56,7 +70,7 @@ Goal: no file in `macOS/DuckDuckGo/` is compiled into a helper target (VPN agent | C | `NetworkProtectionPixelEvent` (569 loc) | app, VPN agent, 3 extensions | `NetworkProtectionMac` → `VPNPixels` (new library product) | `VPNPixels` += `VPN`, `Configuration`; BSK package dep; product linked to 7 targets | 9 imports. Ready, uncommitted (`move-vpn-pixel-event`) | own PR, on top of #7029 | | D | `SubscriptionPixel`, `SubscriptionPixelHandler`, `SubscriptionEnvironment+Default`, `WideEventFeatureFlagAdapter`, `SubscriptionFunnelOrigin` (836 loc) | app, VPN agent, PIR agent, 3 extensions | new local package (no existing one is linked by all consumers) | `Subscription`, `PixelKit`, `Networking`, `PrivacyConfig`, `WideEvent`, `AppKitExtensions`; product linked to ~8 targets | used in ~134 files — biggest diff | own PR (new package, Subscription reviewers) | | E1 | TipKit ×4, `VPNIPCResources`, `VPNOperationErrorRecorder`, `NetworkProtectionVPNCountryLabelsModel`, `VPNSettings+Environment` (~470 loc) | app + VPN agent | `NetworkProtectionMac` (IPC / UI, or a small `TipKitSupport` target) | `Subscription` for `VPNSettings+Environment` | moderate, no strings | combine with G | -| G | 10 helper-only sources living in `DuckDuckGo/` (`Bundle+VPN`, `NetworkProtectionOptionKeyExtension`, `NetworkProtectionControllerErrorStore`, `NetworkProtectionTunnelController`, `MacPacketTunnelProvider`, `MacTransparentProxyProvider`, `NetworkProtectionNotificationsPresenterFactory`, `VPNFailureRecoveryPixel`, `Bundle+NetworkProtectionExtensions`, `SubscriptionManager+StandardConfiguration`) | helpers only, never the app | helper-owned folders | `git mv` + pbxproj group paths only | small, noisy pbxproj | combine with E1 | +| G | 10 helper-only sources living in `DuckDuckGo/` (`Bundle+VPN`, `NetworkProtectionOptionKeyExtension`, `NetworkProtectionControllerErrorStore`, `NetworkProtectionTunnelController`, `MacPacketTunnelProvider`, `MacTransparentProxyProvider`, `NetworkProtectionNotificationsPresenterFactory`, `VPNFailureRecoveryPixel`, `Bundle+NetworkProtectionExtensions`, `SubscriptionManager+StandardConfiguration`) | helpers only, never the app | `DuckDuckGoVPN/`, new `HelperTargetsShared/` (allowed for helper targets in `InputFilesChecker`) | `git mv` + pbxproj group paths only | done | ✅ `alex/move-helper-only-files` | | E2 | `UserText+NetworkProtection+Shared`, `DefaultVPNLocationFormatter` | app + VPN agent | `NetworkProtectionUI` (has `Localizable.xcstrings`) | strings → `Bundle.module` with translations; app and VPN agent each have their own `UserText` | blocked on the strings/resources decision | combine with H | | H | 4 app icons, `macos-config.json`, 7 VPN Lottie JSONs, 2 NetP `.xcstrings` | app + helpers | package resources or per-target copies | resource bundles / `Bundle.module` | same decision as E2 and the app-package tech design | with E2 | | F | `UserDefaultsWrapper` (415 loc) + `UserDefaults+NetworkProtectionShared` | app, VPN agent, all extensions; ~51 app files | don't move — deprecated in favour of `KeyedStoring` | follows the KeyedStoring migration | blocked on that migration (owner/timeline TBD) | after the migration | @@ -70,7 +84,11 @@ Order: C → D → E1+G → strings/resources tech design → E2+H → F after K |---|---|---| | `trim-target-memberships` | `alex/trim-target-memberships` | Stages A+B + membership trim, PR #7029 | | `move-vpn-pixel-event` | `alex/move-vpn-pixel-event` | Stage C, uncommitted, on top of #7029 | -| `drop-sb-feedback`, `drop-sb-navigationbar`, `drop-sb-passwordmanager` | `alex/drop-*-storyboard` | Remaining storyboards | +| `move-helper-only-files` | `alex/move-helper-only-files` | Stage G (committed): helper-only sources → `DuckDuckGoVPN/`, `HelperTargetsShared/` | +| `asset-resource-symbols` | `alex/asset-resource-symbols` | Asset resource initializers (committed) | +| `spm-browser-package` | `alex/spm-browser-package` | Browser code as a SwiftPM package + VSCode setup (WIP, uncommitted) | +| `drop-sb-navigationbar` | `alex/drop-navigationbar-storyboard` | Last storyboard (PR open, merged into `spm-browser-package`) | +| `drop-sb-feedback` | `alex/drop-feedback-storyboard` | Merged (#6866); follow-up commits after the squash? | | `coredata-spm-hacks-cleanup` | `alex/coredata-spm-hacks-cleanup` | no commits ahead of main yet | | `netp-dependencies` | `alex/align-netp-dependencies` | Old draft: move files by target (superseded by the stages above) | | `vscode`, `vscode2` | `alex/fix-imports-cleanup`, `alex/vscode2` | Old: SwiftGenPlugin draft (uncommitted), root `Package.swift` |