From ff51347e336dce742467464094a8cd775b256b7c Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 21 May 2026 02:29:11 +0800 Subject: [PATCH 1/2] Add configurable LookInside server support --- .github/actions/uitests/action.yml | 8 ++ Example/Project.swift | 145 ++++++++++++++++++- Example/README.md | 18 +++ Example/Tuist/Package.resolved | 11 +- Example/Tuist/Package.swift | 221 ++++++++++++++++++++++++----- 5 files changed, 360 insertions(+), 43 deletions(-) diff --git a/.github/actions/uitests/action.yml b/.github/actions/uitests/action.yml index f1f22ee00..595d3f3fc 100644 --- a/.github/actions/uitests/action.yml +++ b/.github/actions/uitests/action.yml @@ -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: | diff --git a/Example/Project.swift b/Example/Project.swift index a3faac921..115d7fb25 100644 --- a/Example/Project.swift +++ b/Example/Project.swift @@ -1,7 +1,127 @@ +import Foundation import ProjectDescription +// 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(_ domain: String, perform: () throws -> T) rethrows -> T { + domains.append(domain) + defer { domains.removeAll { $0 == domain } } + return try perform() + } + + private func envValue(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) +} + +EnvManager.shared.register(domain: "OpenSwiftUI") + // MARK: - Constants +let lookInsideServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER" +let lookInServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER" + +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") +} + let destinations: Destinations = [.iPhone, .iPad, .mac, .appleVision] let deploymentTargets = DeploymentTargets.multiplatform( @@ -50,12 +170,18 @@ let openSwiftUIDebugModeSettings = openSwiftUIModeSettings.merging([ "SWIFT_OPTIMIZATION_LEVEL": "-Onone", ]) +let debugServerReleaseExclusionSettings: 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(debugServerReleaseExclusionSettings), xcconfig: xcconfig), .debug(name: openSwiftUIDebug, settings: openSwiftUIDebugModeSettings, xcconfig: xcconfig), - .release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings, xcconfig: xcconfig), + .release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings.merging(debugServerReleaseExclusionSettings), xcconfig: xcconfig), ] } @@ -184,10 +310,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 let testArguments = Arguments.arguments( environmentVariables: [ diff --git a/Example/README.md b/Example/README.md index b3bec6b53..dcfc6ffc3 100644 --- a/Example/README.md +++ b/Example/README.md @@ -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. diff --git a/Example/Tuist/Package.resolved b/Example/Tuist/Package.resolved index b2741d55c..3d86b56fe 100644 --- a/Example/Tuist/Package.resolved +++ b/Example/Tuist/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "154116076f9106391f665916f636f6142e20264d7bdbff93d21cf58c63c1c2fc", + "originHash" : "cfeb7c8d9b98cf02c5164682b7147b6a863867e3bc31c6852b7bb30d46df4994", "pins" : [ { "identity" : "equatable", @@ -10,6 +10,15 @@ "revision" : "8f1dccead65d97173ed695c6c1ba9d0570d35619" } }, + { + "identity" : "lookinside-release", + "kind" : "remoteSourceControl", + "location" : "https://github.com/LookInsideApp/LookInside-Release.git", + "state" : { + "revision" : "7514cfa8bd24a78f74d8b982c429e7d61ef58134", + "version" : "0.2.2" + } + }, { "identity" : "opencoregraphics", "kind" : "remoteSourceControl", diff --git a/Example/Tuist/Package.swift b/Example/Tuist/Package.swift index ac4bbdabe..ed5ec88f2 100644 --- a/Example/Tuist/Package.swift +++ b/Example/Tuist/Package.swift @@ -2,20 +2,149 @@ import PackageDescription -let package = Package( +// MARK: - EnvManager + +public protocol EnvironmentProvider { + func value(forKey key: String) -> String? +} + +public struct PackageContextEnvironmentProvider: EnvironmentProvider { + public init() {} + + public func value(forKey key: String) -> String? { + Context.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 = PackageContextEnvironmentProvider() + } + + /// 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 = PackageContextEnvironmentProvider() + } + + public func register(domain: String) { + domains.append(domain) + } + + public func withDomain(_ domain: String, perform: () throws -> T) rethrows -> T { + domains.append(domain) + defer { domains.removeAll { $0 == domain } } + return try perform() + } + + private func envValue(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) +} + +EnvManager.shared.register(domain: "OpenSwiftUI") + +let lookInsideServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER" +let lookInServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER" + +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") +} + +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 @@ -40,33 +169,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, From 9cafecc7ec81824a074966408079736bf2597157 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 21 May 2026 02:51:20 +0800 Subject: [PATCH 2/2] Share LookInside server environment config --- Example/Project.swift | 130 ++--------------- Example/Tuist/Package.resolved | 11 +- Example/Tuist/Package.swift | 119 ++------------- .../EnvManager.swift | 136 ++++++++++++++++++ 4 files changed, 155 insertions(+), 241 deletions(-) create mode 100644 Example/Tuist/ProjectDescriptionHelpers/EnvManager.swift diff --git a/Example/Project.swift b/Example/Project.swift index 115d7fb25..3841c8426 100644 --- a/Example/Project.swift +++ b/Example/Project.swift @@ -1,126 +1,12 @@ -import Foundation import ProjectDescription - -// 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(_ domain: String, perform: () throws -> T) rethrows -> T { - domains.append(domain) - defer { domains.removeAll { $0 == domain } } - return try perform() - } - - private func envValue(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) -} - -EnvManager.shared.register(domain: "OpenSwiftUI") +import ProjectDescriptionHelpers // MARK: - Constants -let lookInsideServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER" -let lookInServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER" - -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") -} +configureOpenSwiftUIEnvironment() +let exampleServerConfiguration = OpenSwiftUIExampleServerConfiguration.resolve() +let enableLookInsideServer = exampleServerConfiguration.enableLookInsideServer +let enableLookInServer = exampleServerConfiguration.enableLookInServer let destinations: Destinations = [.iPhone, .iPad, .mac, .appleVision] @@ -170,7 +56,7 @@ let openSwiftUIDebugModeSettings = openSwiftUIModeSettings.merging([ "SWIFT_OPTIMIZATION_LEVEL": "-Onone", ]) -let debugServerReleaseExclusionSettings: SettingsDictionary = (enableLookInsideServer || enableLookInServer) +let inspectUIServerReleaseExclusionSettings: SettingsDictionary = (enableLookInsideServer || enableLookInServer) ? [ "EXCLUDED_SOURCE_FILE_NAMES": "$(inherited) LookInsideServer* LookinServer*", ] @@ -179,9 +65,9 @@ let debugServerReleaseExclusionSettings: SettingsDictionary = (enableLookInsideS func targetConfigurations(_ xcconfig: Path) -> [Configuration] { [ .debug(name: swiftUIDebug, settings: swiftUIModeSettings.merging(debugModeSettings), xcconfig: xcconfig), - .release(name: swiftUIRelease, settings: swiftUIModeSettings.merging(debugServerReleaseExclusionSettings), xcconfig: xcconfig), + .release(name: swiftUIRelease, settings: swiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig), .debug(name: openSwiftUIDebug, settings: openSwiftUIDebugModeSettings, xcconfig: xcconfig), - .release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings.merging(debugServerReleaseExclusionSettings), xcconfig: xcconfig), + .release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig), ] } diff --git a/Example/Tuist/Package.resolved b/Example/Tuist/Package.resolved index 3d86b56fe..96d7f8fc3 100644 --- a/Example/Tuist/Package.resolved +++ b/Example/Tuist/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "cfeb7c8d9b98cf02c5164682b7147b6a863867e3bc31c6852b7bb30d46df4994", + "originHash" : "7ca1a1e556159cedef2e6b4648b27086aa27dbdb32eb4de468b034ec81c5b69f", "pins" : [ { "identity" : "equatable", @@ -10,15 +10,6 @@ "revision" : "8f1dccead65d97173ed695c6c1ba9d0570d35619" } }, - { - "identity" : "lookinside-release", - "kind" : "remoteSourceControl", - "location" : "https://github.com/LookInsideApp/LookInside-Release.git", - "state" : { - "revision" : "7514cfa8bd24a78f74d8b982c429e7d61ef58134", - "version" : "0.2.2" - } - }, { "identity" : "opencoregraphics", "kind" : "remoteSourceControl", diff --git a/Example/Tuist/Package.swift b/Example/Tuist/Package.swift index ed5ec88f2..af026f0cf 100644 --- a/Example/Tuist/Package.swift +++ b/Example/Tuist/Package.swift @@ -2,11 +2,8 @@ import PackageDescription -// MARK: - EnvManager - -public protocol EnvironmentProvider { - func value(forKey key: String) -> String? -} +#if TUIST +import ProjectDescriptionHelpers public struct PackageContextEnvironmentProvider: EnvironmentProvider { public init() {} @@ -16,110 +13,14 @@ public struct PackageContextEnvironmentProvider: EnvironmentProvider { } } -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 = PackageContextEnvironmentProvider() - } - - /// 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 = PackageContextEnvironmentProvider() - } - - public func register(domain: String) { - domains.append(domain) - } - - public func withDomain(_ domain: String, perform: () throws -> T) rethrows -> T { - domains.append(domain) - defer { domains.removeAll { $0 == domain } } - return try perform() - } - - private func envValue(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) -} - -EnvManager.shared.register(domain: "OpenSwiftUI") - -let lookInsideServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKINSIDE_SERVER" -let lookInServerEnvKey = "OPENSWIFTUI_EXAMPLE_LOOKIN_SERVER" - -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") -} +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: "../../"), diff --git a/Example/Tuist/ProjectDescriptionHelpers/EnvManager.swift b/Example/Tuist/ProjectDescriptionHelpers/EnvManager.swift new file mode 100644 index 000000000..673a32208 --- /dev/null +++ b/Example/Tuist/ProjectDescriptionHelpers/EnvManager.swift @@ -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(_ domain: String, perform: () throws -> T) rethrows -> T { + domains.append(domain) + defer { domains.removeAll { $0 == domain } } + return try perform() + } + + private func envValue(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 + ) + } +}