Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/actions/uitests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ runs:
run: Scripts/CI/darwin_setup_build.sh
shell: bash

- name: Disable LookInside & LookIn servers
shell: bash
run: |
{
echo "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER=0"
echo "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER=0"
} >> "$GITHUB_ENV"

- name: Generate Example project
shell: bash
run: |
Expand Down
31 changes: 28 additions & 3 deletions Example/Project.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import ProjectDescription
import ProjectDescriptionHelpers

// MARK: - Constants

configureOpenSwiftUIEnvironment()
let exampleServerConfiguration = OpenSwiftUIExampleServerConfiguration.resolve()
let enableLookInsideServer = exampleServerConfiguration.enableLookInsideServer
let enableLookInServer = exampleServerConfiguration.enableLookInServer

let destinations: Destinations = [.iPhone, .iPad, .mac, .appleVision]

let deploymentTargets = DeploymentTargets.multiplatform(
Expand Down Expand Up @@ -50,12 +56,18 @@ let openSwiftUIDebugModeSettings = openSwiftUIModeSettings.merging([
"SWIFT_OPTIMIZATION_LEVEL": "-Onone",
])

let inspectUIServerReleaseExclusionSettings: SettingsDictionary = (enableLookInsideServer || enableLookInServer)
? [
"EXCLUDED_SOURCE_FILE_NAMES": "$(inherited) LookInsideServer* LookinServer*",
]
: [:]

func targetConfigurations(_ xcconfig: Path) -> [Configuration] {
[
.debug(name: swiftUIDebug, settings: swiftUIModeSettings.merging(debugModeSettings), xcconfig: xcconfig),
.release(name: swiftUIRelease, settings: swiftUIModeSettings, xcconfig: xcconfig),
.release(name: swiftUIRelease, settings: swiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig),
.debug(name: openSwiftUIDebug, settings: openSwiftUIDebugModeSettings, xcconfig: xcconfig),
.release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings, xcconfig: xcconfig),
.release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig),
]
}

Expand Down Expand Up @@ -184,10 +196,23 @@ let privateFrameworkDependencies: [TargetDependency] = [
.external(name: "BacklightServices", condition: .when([.ios, .visionos])),
]

let debugServerDependencies: [TargetDependency]
if enableLookInsideServer {
debugServerDependencies = [
.external(name: "LookInsideServer", condition: .when([.ios, .macos])),
]
} else if enableLookInServer {
debugServerDependencies = [
.external(name: "LookinServer", condition: .when([.ios])),
]
} else {
debugServerDependencies = []
}

let appDependencies: [TargetDependency] = [
.external(name: "OpenSwiftUI"),
.external(name: "Equatable"),
] + privateFrameworkDependencies
] + privateFrameworkDependencies + debugServerDependencies
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appDependencies always includes debugServerDependencies, so Release configs may still link/embed LookInside/Lookin frameworks even though only some source files are excluded. If the intent is “Debug targets only”, you may want to ensure Release configurations don’t carry these dependencies at all.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


let testArguments = Arguments.arguments(
environmentVariables: [
Expand Down
18 changes: 18 additions & 0 deletions Example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ mise exec -- tuist install
mise exec -- tuist generate --no-open
```

By default, the generated Debug Example targets include LookInsideServer. You can switch the debug inspector server before running `tuist install` and `tuist generate`:

```shell
# Default
export OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER=1
export OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER=0

# Use Lookin instead
export OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER=0
export OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER=1

# Disable both
export OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER=0
export OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER=0
```

Do not enable both server variables at the same time.

## Example

A OpenSwiftUI/SwiftUI `App` lifecycle example.
Expand Down
2 changes: 1 addition & 1 deletion Example/Tuist/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 83 additions & 39 deletions Example/Tuist/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,50 @@

import PackageDescription

let package = Package(
#if TUIST
import ProjectDescriptionHelpers

public struct PackageContextEnvironmentProvider: EnvironmentProvider {
public init() {}

public func value(forKey key: String) -> String? {
Context.environment[key]
}
}

configureOpenSwiftUIEnvironment(provider: PackageContextEnvironmentProvider())
let exampleServerConfiguration = OpenSwiftUIExampleServerConfiguration.resolve()
let enableLookInsideServer = exampleServerConfiguration.enableLookInsideServer
let enableLookInServer = exampleServerConfiguration.enableLookInServer
#else
let enableLookInsideServer = false
let enableLookInServer = false
#endif

var dependencies: [PackageDescription.Package.Dependency] = [
.package(path: "../../"),
.package(path: "../../../OpenAttributeGraph"),
.package(path: "../../../OpenRenderBox"),
.package(path: "../../../DarwinPrivateFrameworks"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.0"),
.package(url: "https://github.com/apple/swift-numerics", from: "1.0.3"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "601.0.0"),
.package(url: "https://github.com/OpenSwiftUIProject/equatable.git", branch: "main"),
.package(url: "https://github.com/OpenSwiftUIProject/SymbolLocator.git", from: "0.2.0"),
.package(url: "https://github.com/OpenSwiftUIProject/swift-snapshot-testing", exact: "1.18.9-osui"),
]

if enableLookInsideServer {
dependencies.append(.package(url: "https://github.com/LookInsideApp/LookInside-Release.git", from: "0.2.2"))
}

if enableLookInServer {
dependencies.append(.package(url: "https://github.com/QMUI/LookinServer.git", from: "1.2.8"))
}

let package = PackageDescription.Package(
name: "ExampleDependencies",
dependencies: [
.package(path: "../../"),
.package(path: "../../../OpenAttributeGraph"),
.package(path: "../../../OpenRenderBox"),
.package(path: "../../../DarwinPrivateFrameworks"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.0"),
.package(url: "https://github.com/apple/swift-numerics", from: "1.0.3"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "601.0.0"),
.package(url: "https://github.com/OpenSwiftUIProject/equatable.git", branch: "main"),
.package(url: "https://github.com/OpenSwiftUIProject/SymbolLocator.git", from: "0.2.0"),
.package(url: "https://github.com/OpenSwiftUIProject/swift-snapshot-testing", exact: "1.18.9-osui"),
]
dependencies: dependencies
)

#if TUIST
Expand All @@ -40,33 +70,47 @@ let openSwiftUITargetSettings: SettingsDictionary = [
"DYLIB_INSTALL_NAME_BASE": "@rpath",
]

var packageProductTypes: [String: ProjectDescription.Product] = [
"OpenSwiftUI": ProjectDescription.Product.framework,
"OpenSwiftUICore": ProjectDescription.Product.staticFramework,
"OpenSwiftUI_SPI": ProjectDescription.Product.staticFramework,
"COpenSwiftUI": ProjectDescription.Product.staticFramework,
"OpenSwiftUIMacros": ProjectDescription.Product.macro,
"OpenSwiftUITestsSupport": ProjectDescription.Product.staticFramework,
"OpenSwiftUISymbolDualTestsSupport": ProjectDescription.Product.staticFramework,
"OpenAttributeGraphShims": ProjectDescription.Product.staticFramework,
"OpenCoreGraphicsShims": ProjectDescription.Product.staticFramework,
"OpenObservation": ProjectDescription.Product.staticFramework,
"OpenQuartzCoreShims": ProjectDescription.Product.staticFramework,
"OpenRenderBoxShims": ProjectDescription.Product.staticFramework,
"SymbolLocator": ProjectDescription.Product.staticFramework,
]

var packageProductDestinations: [String: Destinations] = [
"OpenSwiftUI": examplePackageDestinations,
"OpenSwiftUICore": examplePackageDestinations,
"OpenSwiftUI_SPI": examplePackageDestinations,
"OpenSwiftUIExtension": examplePackageDestinations,
"OpenSwiftUIBridge": examplePackageDestinations,
"OpenAttributeGraph": examplePackageDestinations,
"OpenAttributeGraphShims": examplePackageDestinations,
"OpenRenderBox": examplePackageDestinations,
"OpenRenderBoxShims": examplePackageDestinations,
]

if enableLookInsideServer {
packageProductTypes["LookInsideServer"] = ProjectDescription.Product.framework
packageProductDestinations["LookInsideServer"] = [.iPhone, .iPad, .mac]
}

if enableLookInServer {
packageProductTypes["LookinServer"] = ProjectDescription.Product.framework
packageProductDestinations["LookinServer"] = [.iPhone, .iPad]
}

let packageSettings = PackageSettings(
productTypes: [
"OpenSwiftUI": ProjectDescription.Product.framework,
"OpenSwiftUICore": ProjectDescription.Product.staticFramework,
"OpenSwiftUI_SPI": ProjectDescription.Product.staticFramework,
"COpenSwiftUI": ProjectDescription.Product.staticFramework,
"OpenSwiftUIMacros": ProjectDescription.Product.macro,
"OpenSwiftUITestsSupport": ProjectDescription.Product.staticFramework,
"OpenSwiftUISymbolDualTestsSupport": ProjectDescription.Product.staticFramework,
"OpenAttributeGraphShims": ProjectDescription.Product.staticFramework,
"OpenCoreGraphicsShims": ProjectDescription.Product.staticFramework,
"OpenObservation": ProjectDescription.Product.staticFramework,
"OpenQuartzCoreShims": ProjectDescription.Product.staticFramework,
"OpenRenderBoxShims": ProjectDescription.Product.staticFramework,
"SymbolLocator": ProjectDescription.Product.staticFramework,
],
productDestinations: [
"OpenSwiftUI": examplePackageDestinations,
"OpenSwiftUICore": examplePackageDestinations,
"OpenSwiftUI_SPI": examplePackageDestinations,
"OpenSwiftUIExtension": examplePackageDestinations,
"OpenSwiftUIBridge": examplePackageDestinations,
"OpenAttributeGraph": examplePackageDestinations,
"OpenAttributeGraphShims": examplePackageDestinations,
"OpenRenderBox": examplePackageDestinations,
"OpenRenderBoxShims": examplePackageDestinations,
],
productTypes: packageProductTypes,
productDestinations: packageProductDestinations,
baseSettings: .settings(
configurations: openSwiftUIPackageConfigurations,
defaultSettings: .none,
Expand Down
136 changes: 136 additions & 0 deletions Example/Tuist/ProjectDescriptionHelpers/EnvManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import Foundation

// MARK: - EnvManager

public protocol EnvironmentProvider {
func value(forKey key: String) -> String?
}

public struct ProcessInfoEnvironmentProvider: EnvironmentProvider {
public init() {}

public func value(forKey key: String) -> String? {
ProcessInfo.processInfo.environment[key]
}
}

public final class EnvManager {
nonisolated(unsafe) public static let shared = EnvManager()

private var domains: [String] = []
private var environmentProvider: EnvironmentProvider

/// When true, append raw key as fallback when searching in domains
public var includeFallbackToRawKey: Bool = false

private init() {
self.environmentProvider = ProcessInfoEnvironmentProvider()
}

/// Set a custom environment provider (useful for testing)
public func setEnvironmentProvider(_ provider: EnvironmentProvider) {
self.environmentProvider = provider
}

/// Reset domains and environment provider (useful for testing)
public func reset() {
domains.removeAll()
includeFallbackToRawKey = false
self.environmentProvider = ProcessInfoEnvironmentProvider()
}

public func register(domain: String) {
domains.append(domain)
}

public func withDomain<T>(_ domain: String, perform: () throws -> T) rethrows -> T {
domains.append(domain)
defer { domains.removeAll { $0 == domain } }
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withDomain appends domain then defer { domains.removeAll { $0 == domain } }, which will also remove any previously-registered occurrences of the same domain (and can break later lookups). Consider only removing the entry added by this call so nested/duplicate domains don’t wipe global state.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

return try perform()
}

private func envValue<T>(rawKey: String, default defaultValue: T?, searchInDomain: Bool, parser: (String) -> T?) -> T? {
func parseEnvValue(_ key: String) -> (String, T)? {
guard let value = environmentProvider.value(forKey: key),
let result = parser(value) else { return nil }
return (value, result)
}
var keys: [String] = searchInDomain ? domains.map { "\($0.uppercased())_\(rawKey)" } : []
if !searchInDomain || includeFallbackToRawKey {
keys.append(rawKey)
}
for key in keys {
if let (value, result) = parseEnvValue(key) {
print("[Env] \(key)=\(value) -> \(result)")
return result
}
}
let primaryKey = keys.first ?? rawKey
if let defaultValue {
print("[Env] \(primaryKey) not set -> \(defaultValue)(default)")
}
return defaultValue
}

public func envBoolValue(rawKey: String, default defaultValue: Bool? = nil, searchInDomain: Bool) -> Bool? {
envValue(rawKey: rawKey, default: defaultValue, searchInDomain: searchInDomain) { value in
switch value {
case "1": true
case "0": false
default: nil
}
}
}

public func envIntValue(rawKey: String, default defaultValue: Int? = nil, searchInDomain: Bool) -> Int? {
envValue(rawKey: rawKey, default: defaultValue, searchInDomain: searchInDomain) { Int($0) }
}

public func envStringValue(rawKey: String, default defaultValue: String? = nil, searchInDomain: Bool) -> String? {
envValue(rawKey: rawKey, default: defaultValue, searchInDomain: searchInDomain) { $0 }
}
}

public func envBoolValue(_ key: String, default defaultValue: Bool = false, searchInDomain: Bool = true) -> Bool {
EnvManager.shared.envBoolValue(rawKey: key, default: defaultValue, searchInDomain: searchInDomain)!
}

public func envIntValue(_ key: String, default defaultValue: Int = 0, searchInDomain: Bool = true) -> Int {
EnvManager.shared.envIntValue(rawKey: key, default: defaultValue, searchInDomain: searchInDomain)!
}

public func envStringValue(_ key: String, default defaultValue: String, searchInDomain: Bool = true) -> String {
EnvManager.shared.envStringValue(rawKey: key, default: defaultValue, searchInDomain: searchInDomain)!
}

public func envStringValue(_ key: String, searchInDomain: Bool = true) -> String? {
EnvManager.shared.envStringValue(rawKey: key, searchInDomain: searchInDomain)
}

public func configureOpenSwiftUIEnvironment(provider: EnvironmentProvider? = nil) {
EnvManager.shared.reset()
if let provider {
EnvManager.shared.setEnvironmentProvider(provider)
}
EnvManager.shared.register(domain: "OpenSwiftUI")
}

public struct OpenSwiftUIExampleServerConfiguration {
public static let lookInsideServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER"
public static let lookInServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER"

public let enableLookInsideServer: Bool
public let enableLookInServer: Bool

public static func resolve() -> OpenSwiftUIExampleServerConfiguration {
let enableLookInsideServer = envBoolValue("EXAMPLE_LOOKINSIDE_SERVER", default: true)
let enableLookInServer = envBoolValue("EXAMPLE_LOOKIN_SERVER", default: false)
if enableLookInsideServer && enableLookInServer {
fatalError("\(lookInsideServerEnvKey) and \(lookInServerEnvKey) cannot both be enabled")
}
return OpenSwiftUIExampleServerConfiguration(
enableLookInsideServer: enableLookInsideServer,
enableLookInServer: enableLookInServer
)
}
}
Loading