64 lines
6.8 KiB
Markdown
Executable File
64 lines
6.8 KiB
Markdown
Executable File
# VSCode Migration — Technical Findings
|
|
|
|
Back to [[projects/vscode-migration/vscode-migration]]. Verified on Xcode 26.6 / Swift 6.3.3, Sep 2026.
|
|
|
|
## 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).
|
|
- No ObjC/C in `macOS/DuckDuckGo/`, empty bridging header.
|
|
- Unit Tests compile only 2 app files directly; the rest is `@testable import` → can switch to the library.
|
|
- 38 app files (by path; an earlier name-based count said 46) were shared with helper targets. After the membership trim 27 remained; after stages A+B 18. Progress per stage: [[projects/vscode-migration/vscode-migration#Target split stages]].
|
|
- Remaining Xcode-only resources: storyboards `PasswordManager`, `BookmarksBar`, `NavigationBar`, `Feedback` + `BookmarksBarCollectionViewItem.xib`; 8 `.xcdatamodeld` in the app, 3 in BSK.
|
|
|
|
## Target split (files shared with helper targets)
|
|
|
|
- Find out which target really needs a file by removing it from the target and building, not by grepping: 15 of 38 memberships turned out unused (some only transitively needed, e.g. TipKit, Safari/WebKit version readers).
|
|
- DMG and App Store variants must have identical sources: `InputFilesChecker` fails the build otherwise, so remove a file from both.
|
|
- `InputFilesChecker.temporarilyAllowedMisplacedFiles` lists the files still outside their owning target, one block per stage; stale entries fail the build.
|
|
- Local package products are linked as `XCSwiftPackageProductDependency` without a `package` key. Unit Tests don't link `NetworkProtectionMac` products; modules are visible through the host app.
|
|
- Transitive module imports work in Xcode (e.g. `Subscription` inside `DataBrokerProtection-macOS` without a direct product). `DataBrokerProtection-macOS` relies on this, so moved files follow the same convention.
|
|
- `UserText` exists twice (app and VPN agent, `DuckDuckGoVPN/UserText.swift`); shared files extend whichever one the target compiles. Moving them into a package means moving the strings (and translations) to the package's `Localizable.xcstrings`.
|
|
- `UserDefaultsWrapper` is `@deprecated` on main ("Use KeyedStoring pattern") — don't move it, follow that migration.
|
|
- `SANDBOX_TEST_TOOL` / `ALPHA` were dead: packages never receive target `FEATURE_FLAGS`, and sandbox-test-tool compiled only its own file. The system extension always has `AppVersion.runType == .normal`.
|
|
- `NETP_SYSTEM_EXTENSION` remains only in extension-only code (keychain service names, defaults, pixel source).
|
|
|
|
## Build verification (offline)
|
|
|
|
- Build with `-project macOS/DuckDuckGo-macOS.xcodeproj` (as CI does), not the root workspace: on main (late Sep 2026) the root `DuckDuckGo.xcworkspace` `Package.resolved` lacked `swift-http-structured-headers` (added by iOS `SitePermissions`), so workspace builds with `-disableAutomaticPackageResolution` fail.
|
|
- Offline builds: copy `SourcePackages` from a DerivedData whose checkouts contain all pinned revisions, then `-disableAutomaticPackageResolution -skipPackageUpdates -onlyUsePackageVersionsFromResolvedFile -skipPackagePluginValidation -skipMacroValidation CODE_SIGNING_ALLOWED=NO`.
|
|
- Schemes covering all helper targets: `macOS Browser`, `macOS Browser App Store`, `DuckDuckGoVPNSysexAppStore`, `macOS VPN Proxy Extension`, `macOS VPN System Extension`, `macOS VPN Agent App Store`, `macOS PIR Agent`, plus `build-for-testing` for `macOS Unit Tests` / `macOS Integration Tests`.
|
|
- Run SwiftLint from `macOS/` so the project `.swiftlint.yml` applies; running it from subfolders uses defaults and reports noise.
|
|
|
|
## App Store UI tests (sandbox)
|
|
|
|
- `AppStoreSandboxUITests` replaced sandbox-test-tool ([#6980](https://github.com/duckduckgo/apple-browsers/pull/6980)): custom downloads folder outside the auto-granted folders survives relaunch (security-scoped bookmark in `DownloadsPreferences`), and downloaded files stay accessible after relaunch (per-file bookmarks in `DownloadListStore`).
|
|
- The App Store schemes default to `UI Tests App Store Sandbox.xctestplan` (this class only); `UI Tests App Store.xctestplan` is used when CI filters by class name. CI runs the class along every full DMG run; failures create "GH Workflow Failure - App Store UI Tests".
|
|
|
|
## Asset catalogs
|
|
|
|
- Xcode generates asset symbols with `actool --bundle-identifier … --generate-swift-asset-symbols X.swift`. In that mode actool writes no `Assets.car`, so compiling is a second invocation (same as Xcode's `GenerateAssetSymbols` + `CompileAssetCatalog`).
|
|
- For **package** targets Xcode and `swiftbuild` generate `ImageResource`/`ColorResource` symbols but **not** `NSImage.foo` / `NSColor.foo` extensions. The app target uses extensions (e.g. `NSImage.menuVertical16`) → those call sites must change when moving into a package.
|
|
- Native `swift build` (also what SourceKit-LSP uses) neither compiles catalogs nor generates symbols.
|
|
- Native SwiftPM resource bundles are flat and lack Info.plist; CoreUI ignores `Assets.car` without `CFBundleIdentifier`.
|
|
- In Xcode's plugin sandbox actool fails for iOS ("No available simulator runtimes"); macOS works. So a plugin can't replace Xcode's processing on iOS.
|
|
- Plugin environment is identical for native and `swiftbuild` → can't tell them apart; Xcode is detectable by the `/BuildToolPluginIntermediates/` work dir.
|
|
- Asset usage in the app catalog: ~415 via symbols, ~57 via string names, 20 only from storyboards, ~300 apparently unreferenced (heuristic).
|
|
|
|
## Extensions in VSCode tasks
|
|
|
|
- `tasks.json` `dependsOn` has no up-to-date check → needs a gate script (input hash via `git ls-files -s` + diff, stamp file).
|
|
- `xcodebuild` always visits explicit target dependencies (planning, Provisional Inputs) even when nothing changed; no flag to skip. Gating extensions only pays off once the app is built with `swift build` and assembled by a script.
|
|
- Assemble: copy pre-signed extensions, sign the outer app last without `--deep`; same Team ID; embed provisioning profiles for NetworkExtension entitlements; system extension activates only from `/Applications`.
|
|
- Embedded: Login Items `DuckDuckGo VPN.app`, `DuckDuckGo Personal Information Removal.app`; PlugIns `NetworkProtectionAppExtension.appex`, `VPNProxyExtension.appex`.
|
|
|
|
## Toolchain
|
|
|
|
- `~/.swiftly/bin/swift` (6.2) is first in PATH; it can't compile packages importing WebKit against the macOS 26 SDK. Use `xcrun swift`, and set `swift.path` in VSCode to Xcode's toolchain.
|
|
- Native builds rewrite stale `Package.resolved` files (e.g. `SharedPackages/Onboarding`) — don't commit those.
|
|
|
|
## Pre-existing issues found
|
|
|
|
- `UIComponentsTests`: macOS 15 API without `@available` (package min 12.3).
|
|
- `PerformanceTestTests`: reference removed types (`CoreWebVitalsAssessment`, `averageResourceSize`).
|
|
- `DataBrokerProtectionCore`: empty `DBP.xcassets` inside `.copy("BundleResources")` — dead, never compiled.
|