Files
obsidian-vault/work/projects/vscode-migration/findings.md
T

6.8 KiB
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): 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.