From d6d41dfa44ff5022e402a54d123abd91c1a07801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 15 Jan 2026 14:31:43 +0100 Subject: [PATCH 01/10] feat: async shared instances --- Sources/Container/Async/AsyncContainer.swift | 43 ++++++++++++++++---- Tests/Container/Async/AsyncBaseTests.swift | 32 +++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/Sources/Container/Async/AsyncContainer.swift b/Sources/Container/Async/AsyncContainer.swift index d23de47..a61e3c0 100644 --- a/Sources/Container/Async/AsyncContainer.swift +++ b/Sources/Container/Async/AsyncContainer.swift @@ -14,6 +14,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin private var registrations = [RegistrationIdentifier: AsyncRegistration]() private var sharedInstances = [RegistrationIdentifier: Any]() + private var sharedTasks = [RegistrationIdentifier: Task]() /// Create new instance of ``AsyncContainer`` public init() {} @@ -28,6 +29,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin /// Remove already instantiated shared instances from the container public func releaseSharedInstances() { sharedInstances.removeAll() + sharedTasks.removeAll() } // MARK: Register dependency @@ -50,6 +52,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin // With a new registration we should clean all shared instances // because the new registered factory most likely returns different objects and we have no way to tell sharedInstances[registration.identifier] = nil + sharedTasks[registration.identifier] = nil } // MARK: Register dependency with argument @@ -212,6 +215,15 @@ private extension AsyncContainer { if let dependency = sharedInstances[registration.identifier] as? Dependency { return dependency } + + if let task = sharedTasks[registration.identifier] { + do { + return try await task.value as! Dependency + } catch { + sharedTasks[registration.identifier] = nil + throw error + } + } case .new: break } @@ -219,15 +231,32 @@ private extension AsyncContainer { // We use force cast here because we are sure that the type-casting always succeed // The reason why the `factory` closure returns ``Any`` is that we have to erase the generic type in order to store the registration // When the registration is created it can be initialized just with a `factory` that returns the matching type - let dependency = try await registration.asyncRegistrationFactory(self, argument) as! Dependency + let task = Task { + try await registration.asyncRegistrationFactory(self, argument) + } - switch registration.scope { - case .shared: - sharedInstances[registration.identifier] = dependency - case .new: - break + if case .shared = registration.scope { + sharedTasks[registration.identifier] = task + } + + do { + let dependency = try await task.value as! Dependency + + switch registration.scope { + case .shared: + sharedInstances[registration.identifier] = dependency + sharedTasks[registration.identifier] = nil + case .new: + break + } + + return dependency + } catch { + if case .shared = registration.scope { + sharedTasks[registration.identifier] = nil + } + throw error } - return dependency } } diff --git a/Tests/Container/Async/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift index 08a2c30..03a0d94 100644 --- a/Tests/Container/Async/AsyncBaseTests.swift +++ b/Tests/Container/Async/AsyncBaseTests.swift @@ -107,4 +107,36 @@ struct AsyncBaseTests { } } } + + @Test func concurrentResolveSharedDependency() async { + // Given + let subject = AsyncContainer() + await subject.register(in: .shared) { _ -> SimpleDependency in + SimpleDependency() + } + + // When + let resolvedDependencies = await withTaskGroup(of: SimpleDependency.self) { group in + for _ in 0..<50 { + group.addTask { + await subject.resolve() + } + } + + var dependencies: [SimpleDependency] = [] + for await dependency in group { + dependencies.append(dependency) + } + + return dependencies + } + + // Then + guard let firstDependency = resolvedDependencies.first else { + Issue.record("Expected to resolve at least one dependency") + return + } + + #expect(resolvedDependencies.allSatisfy { $0 === firstDependency }) + } } From af55e8e5443ef83e4e015b387f1a0fb43850aa60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 15 Jan 2026 15:11:18 +0100 Subject: [PATCH 02/10] feat: another async test --- Tests/Container/Async/AsyncBaseTests.swift | 60 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/Tests/Container/Async/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift index 974edb6..1c02f09 100644 --- a/Tests/Container/Async/AsyncBaseTests.swift +++ b/Tests/Container/Async/AsyncBaseTests.swift @@ -115,7 +115,8 @@ struct AsyncBaseTests { } } - @Test func concurrentResolveSharedDependency() async { + @Test("Concurrent resolve of shared dependency") + func concurrentResolveSharedDependency() async { // Given let subject = AsyncContainer() await subject.register(in: .shared) { _ -> SimpleDependency in @@ -146,4 +147,61 @@ struct AsyncBaseTests { #expect(resolvedDependencies.allSatisfy { $0 === firstDependency }) } + + @Test("Concurrent register and resolve different types") + func concurrentRegisterAndResolveDifferentTypes() async { + // Given + let subject = AsyncContainer() + + // When + let resolvedDependencies = await withTaskGroup(of: Any.self) { group in + group.addTask { + await subject.register(in: .shared) { _ -> SimpleDependency in + SimpleDependency() + } + + let dependency: SimpleDependency = await subject.resolve() + return dependency + } + + group.addTask { + await subject.register { _, argument -> DependencyWithValueTypeParameter in + DependencyWithValueTypeParameter(subDependency: argument) + } + + let argument = StructureDependency(property1: "concurrent") + let dependency: DependencyWithValueTypeParameter = await subject.resolve(argument: argument) + return dependency + } + + group.addTask { + await subject.register { _, argument -> DependencyWithAsyncInitWithParameter in + await DependencyWithAsyncInitWithParameter(subDependency: argument) + } + + let argument = StructureDependency(property1: "async") + let dependency: DependencyWithAsyncInitWithParameter = await subject.resolve(argument: argument) + return dependency + } + + var dependencies: [Any] = [] + for await dependency in group { + dependencies.append(dependency) + } + + return dependencies + } + + // Then + #expect(resolvedDependencies.count == 3) + + let hasSimpleDependency = resolvedDependencies.contains { $0 is SimpleDependency } + #expect(hasSimpleDependency) + + let hasValueTypeParameter = resolvedDependencies.contains { $0 is DependencyWithValueTypeParameter } + #expect(hasValueTypeParameter) + + let hasAsyncInitParameter = resolvedDependencies.contains { $0 is DependencyWithAsyncInitWithParameter } + #expect(hasAsyncInitParameter) + } } From 5f54fdf2f91d75687f8c885f0b609897ad23eda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 19 Jan 2026 11:35:24 +0100 Subject: [PATCH 03/10] feat: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd90dc9..2dfd47b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ __Sections__ ### Fixed - Support for multiple arguments. Code clean up. +- Tests are converted to the new Swift Testing framework ## [1.0.4] From 5713fdf4df53676069478c51bad2015f0b092f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 19 Jan 2026 11:52:47 +0100 Subject: [PATCH 04/10] feat: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dfd47b..c697402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ __Sections__ - Support for multiple arguments. Code clean up. - Tests are converted to the new Swift Testing framework +- Fix of async resolve of shared dependencies ## [1.0.4] From cccd2267b48b77327907d398440ad5d515b411ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 9 Feb 2026 12:36:11 +0100 Subject: [PATCH 05/10] feat: update --- Tests/Container/Async/AsyncBaseTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Container/Async/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift index f672586..a521154 100644 --- a/Tests/Container/Async/AsyncBaseTests.swift +++ b/Tests/Container/Async/AsyncBaseTests.swift @@ -170,7 +170,7 @@ struct AsyncBaseTests { } let argument = StructureDependency(property1: "concurrent") - let dependency: DependencyWithValueTypeParameter = await subject.resolve(argument: argument) + let dependency: DependencyWithValueTypeParameter = await subject.resolve(argument) return dependency } @@ -180,7 +180,7 @@ struct AsyncBaseTests { } let argument = StructureDependency(property1: "async") - let dependency: DependencyWithAsyncInitWithParameter = await subject.resolve(argument: argument) + let dependency: DependencyWithAsyncInitWithParameter = await subject.resolve(argument) return dependency } From 6b09e0114846fc7f24a2326564c7420ab4b8ec23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 9 Feb 2026 13:36:39 +0100 Subject: [PATCH 06/10] feat: arguments --- Tests/Container/Async/AsyncBaseTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Container/Async/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift index a521154..f997590 100644 --- a/Tests/Container/Async/AsyncBaseTests.swift +++ b/Tests/Container/Async/AsyncBaseTests.swift @@ -170,7 +170,7 @@ struct AsyncBaseTests { } let argument = StructureDependency(property1: "concurrent") - let dependency: DependencyWithValueTypeParameter = await subject.resolve(argument) + let dependency: DependencyWithValueTypeParameter = await subject.resolve(arguments: argument) return dependency } @@ -180,7 +180,7 @@ struct AsyncBaseTests { } let argument = StructureDependency(property1: "async") - let dependency: DependencyWithAsyncInitWithParameter = await subject.resolve(argument) + let dependency: DependencyWithAsyncInitWithParameter = await subject.resolve(arguments: argument) return dependency } From 8e38d212a91f21f23302ba026f71f35816f5db61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 9 Feb 2026 18:42:09 +0100 Subject: [PATCH 07/10] feat: registration changes --- .../Async/AsyncDependencyRegistering.swift | 13 +++++++++++++ .../Registration/Sync/DependencyRegistering.swift | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift b/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift index f1e1247..b752448 100644 --- a/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift +++ b/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift @@ -38,6 +38,19 @@ public protocol AsyncDependencyRegistering { // MARK: Overloaded factory methods public extension AsyncDependencyRegistering { + /// Register a dependency with type and a single-parameter factory (resolver only). + /// + /// This overload ensures that `register(type: X.self) { _ in ... }` creates a zero-argument registration, + /// so that `resolve()` without arguments succeeds. Without it, the compiler may bind to the parameter-pack + /// overload and infer a one-argument registration, causing `unmatchingArgumentType` when resolving with no arguments. + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - factory: Closure that is called when the dependency is being resolved (receives the resolver only) + func register(type: Dependency.Type, factory: @escaping Factory) async { + await register(type: type, in: .new, factory: factory) + } + /// Register a dependency with an implicit type determined by the factory closure return type /// /// - Parameters: diff --git a/Sources/Protocols/Registration/Sync/DependencyRegistering.swift b/Sources/Protocols/Registration/Sync/DependencyRegistering.swift index d920326..6668e60 100644 --- a/Sources/Protocols/Registration/Sync/DependencyRegistering.swift +++ b/Sources/Protocols/Registration/Sync/DependencyRegistering.swift @@ -38,6 +38,19 @@ public protocol DependencyRegistering { // MARK: Overloaded factory methods public extension DependencyRegistering { + /// Register a dependency with type and a single-parameter factory (resolver only). + /// + /// This overload ensures that `register(type: X.self) { _ in ... }` creates a zero-argument registration, + /// so that `resolve()` without arguments succeeds. Without it, the compiler may bind to the parameter-pack + /// overload and infer a one-argument registration, causing `unmatchingArgumentType` when resolving with no arguments. + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - factory: Closure that is called when the dependency is being resolved (receives the resolver only) + func register(type: Dependency.Type, factory: @escaping Factory) { + register(type: type, in: .new, factory: factory) + } + /// Register a dependency with an implicit type determined by the factory closure return type /// /// - Parameters: From 0f858eb8dbab5df4b03ba179adc1114c17385c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 12 Feb 2026 17:25:05 +0100 Subject: [PATCH 08/10] feat: explicit scope --- Sources/Container/Async/AsyncContainer.swift | 2 +- Sources/Container/Sync/Container.swift | 2 +- .../Async/AsyncDependencyRegistering.swift | 13 ------------- .../Registration/Sync/DependencyRegistering.swift | 13 ------------- Tests/Container/Async/AsyncBaseTests.swift | 15 +++++++++++++++ Tests/Container/Sync/BaseTests.swift | 15 +++++++++++++++ 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Sources/Container/Async/AsyncContainer.swift b/Sources/Container/Async/AsyncContainer.swift index b475d2f..7a2225a 100644 --- a/Sources/Container/Async/AsyncContainer.swift +++ b/Sources/Container/Async/AsyncContainer.swift @@ -136,7 +136,7 @@ private extension AsyncContainer { } - func getDependency(from registration: AsyncRegistration, with argument: Any? = nil) async throws -> Dependency { + func getDependency(from registration: AsyncRegistration, with argument: Any? = ()) async throws -> Dependency { switch registration.scope { case .shared: if let dependency = sharedInstances[registration.identifier] as? Dependency { diff --git a/Sources/Container/Sync/Container.swift b/Sources/Container/Sync/Container.swift index b56c5ec..4a44920 100644 --- a/Sources/Container/Sync/Container.swift +++ b/Sources/Container/Sync/Container.swift @@ -129,7 +129,7 @@ private extension Container { } - func getDependency(from registration: Registration, with argument: Any? = nil) throws -> Dependency { + func getDependency(from registration: Registration, with argument: Any? = ()) throws -> Dependency { switch registration.scope { case .shared: if let dependency = sharedInstances[registration.identifier] as? Dependency { diff --git a/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift b/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift index b752448..f1e1247 100644 --- a/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift +++ b/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift @@ -38,19 +38,6 @@ public protocol AsyncDependencyRegistering { // MARK: Overloaded factory methods public extension AsyncDependencyRegistering { - /// Register a dependency with type and a single-parameter factory (resolver only). - /// - /// This overload ensures that `register(type: X.self) { _ in ... }` creates a zero-argument registration, - /// so that `resolve()` without arguments succeeds. Without it, the compiler may bind to the parameter-pack - /// overload and infer a one-argument registration, causing `unmatchingArgumentType` when resolving with no arguments. - /// - /// - Parameters: - /// - type: Type of the dependency to register - /// - factory: Closure that is called when the dependency is being resolved (receives the resolver only) - func register(type: Dependency.Type, factory: @escaping Factory) async { - await register(type: type, in: .new, factory: factory) - } - /// Register a dependency with an implicit type determined by the factory closure return type /// /// - Parameters: diff --git a/Sources/Protocols/Registration/Sync/DependencyRegistering.swift b/Sources/Protocols/Registration/Sync/DependencyRegistering.swift index 6668e60..d920326 100644 --- a/Sources/Protocols/Registration/Sync/DependencyRegistering.swift +++ b/Sources/Protocols/Registration/Sync/DependencyRegistering.swift @@ -38,19 +38,6 @@ public protocol DependencyRegistering { // MARK: Overloaded factory methods public extension DependencyRegistering { - /// Register a dependency with type and a single-parameter factory (resolver only). - /// - /// This overload ensures that `register(type: X.self) { _ in ... }` creates a zero-argument registration, - /// so that `resolve()` without arguments succeeds. Without it, the compiler may bind to the parameter-pack - /// overload and infer a one-argument registration, causing `unmatchingArgumentType` when resolving with no arguments. - /// - /// - Parameters: - /// - type: Type of the dependency to register - /// - factory: Closure that is called when the dependency is being resolved (receives the resolver only) - func register(type: Dependency.Type, factory: @escaping Factory) { - register(type: type, in: .new, factory: factory) - } - /// Register a dependency with an implicit type determined by the factory closure return type /// /// - Parameters: diff --git a/Tests/Container/Async/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift index f997590..6fa4f10 100644 --- a/Tests/Container/Async/AsyncBaseTests.swift +++ b/Tests/Container/Async/AsyncBaseTests.swift @@ -204,4 +204,19 @@ struct AsyncBaseTests { let hasAsyncInitParameter = resolvedDependencies.contains { $0 is DependencyWithAsyncInitWithParameter } #expect(hasAsyncInitParameter) } + + @Test("Register explicit type with resolver-only factory") + func registerExplicitTypeWithResolverOnlyFactory() async throws { + // Given + let subject = AsyncContainer() + await subject.register(type: SimpleDependency.self) { _ in + SimpleDependency() + } + + // When + let resolvedDependency = try await subject.tryResolve(type: SimpleDependency.self) + + // Then + #expect(resolvedDependency is SimpleDependency) + } } diff --git a/Tests/Container/Sync/BaseTests.swift b/Tests/Container/Sync/BaseTests.swift index 18e0dc0..2b49b9e 100644 --- a/Tests/Container/Sync/BaseTests.swift +++ b/Tests/Container/Sync/BaseTests.swift @@ -156,4 +156,19 @@ struct BaseTests { } } } + + @Test("Register explicit type with resolver-only factory") + func registerExplicitTypeWithResolverOnlyFactory() throws { + // Given + let subject = Container() + subject.register(type: SimpleDependency.self) { _ in + SimpleDependency() + } + + // When + let resolvedDependency: SimpleDependency = try subject.tryResolve(type: SimpleDependency.self) + + // Then + #expect(resolvedDependency is SimpleDependency) + } } From 755e4e37fb961567bb4b8692a3821a63d3e6a42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 5 Mar 2026 13:01:11 +0100 Subject: [PATCH 09/10] remove: scripts --- .githooks/pre-commit | 4 - scripts/ModuleGenerator.swift | 404 ----------------- scripts/git-format-staged | 282 ------------ scripts/rename.sh | 40 -- scripts/setup_bundler.sh | 11 - scripts/setup_ci.sh | 30 -- scripts/setup_tuist_githook.sh | 23 - scripts/swiftformat.sh | 43 -- scripts/swiftlint.sh | 41 -- scripts/update_dependencies.rb | 128 ------ scripts/verify_ruby.sh | 105 ----- setup.swift | 761 --------------------------------- 12 files changed, 1872 deletions(-) delete mode 100755 .githooks/pre-commit delete mode 100755 scripts/ModuleGenerator.swift delete mode 100755 scripts/git-format-staged delete mode 100755 scripts/rename.sh delete mode 100755 scripts/setup_bundler.sh delete mode 100755 scripts/setup_ci.sh delete mode 100755 scripts/setup_tuist_githook.sh delete mode 100755 scripts/swiftformat.sh delete mode 100755 scripts/swiftlint.sh delete mode 100644 scripts/update_dependencies.rb delete mode 100755 scripts/verify_ruby.sh delete mode 100755 setup.swift diff --git a/.githooks/pre-commit b/.githooks/pre-commit deleted file mode 100755 index 73a926f..0000000 --- a/.githooks/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -cd "$(git rev-parse --show-toplevel)" -./scripts/swiftformat.sh \ No newline at end of file diff --git a/scripts/ModuleGenerator.swift b/scripts/ModuleGenerator.swift deleted file mode 100755 index 95d2153..0000000 --- a/scripts/ModuleGenerator.swift +++ /dev/null @@ -1,404 +0,0 @@ -#!/usr/bin/swift -import Foundation - -// swiftlint:disable disable_print - -// This enables the script to exit when the user presses Ctrl+C -signal(SIGINT) { _ in exit(0) } - -// MARK: - Enums - -enum LayerType: String, CaseIterable { - case feature = "Feature" - case service = "Service" - case library = "Library" -} - -enum ModuleGenerationError: Error { - case failedToReadDependencyFile - case failedToWriteDependencyFile - case invalidInsertionPoint - case failedToReadTuistOutput -} - -// MARK: - Structs - -struct ModuleInfo { - let layer: LayerType - let moduleName: String - let hasInterface: Bool - let hasTestResources: Bool - let hasUnitTests: Bool - let hasUITests: Bool - let hasExample: Bool - let hasLocalizedStrings: Bool - let hasAssets: Bool -} - -// MARK: - Module Generator - -/// This class utilizes Tuist's `scaffold` command to generate the basic structure and files -/// for new modules in a modularized project. It automates the process of creating modules, -/// their interfaces, tests, and example targets based on provided specifications. -final class ModuleGenerator { - private let fileManager = FileManager.default - private let currentPath = "./" - - func generateModule() { - do { - let moduleInfo = getModuleInfo() - try scaffoldModule(moduleInfo) - try registerDependency(moduleInfo) - - let createdTargets = listCreatedTargets(moduleInfo) - print("✅ Module '\(moduleInfo.moduleName)' created successfully in the \(moduleInfo.layer.rawValue) layer!") - print(" Created targets: \(createdTargets)") - } catch { - print("❌ Error creating module: \(error)") - } - } -} - -private extension ModuleGenerator { - func getModuleInfo() -> ModuleInfo { - print("\u{001B}[1mWhat's the layer of this module?\u{001B}[0m") - for (index, layer) in LayerType.allCases.enumerated() { - print("\(index + 1). \(layer.rawValue)") - } - - let layer = LayerType.allCases[getIntInput(min: 1, max: LayerType.allCases.count) - 1] - - print("\u{001B}[1mEnter module name:\u{001B}[0m", terminator: " ") - let moduleName = readLine()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - - let hasResources = getBoolInput("Do you want to include resources (strings or assets) in this module?") - var hasLocalizedStrings = false - var hasAssets = false - - if hasResources { - hasLocalizedStrings = getBoolInput("Do you want to include localized strings?") - hasAssets = getBoolInput("Do you want to include an Asset catalog (for colors and images)?") - } - - let hasInterface = getBoolInput("Do you want to include an Interface target?") - let hasUnitTests = getBoolInput("Do you want to include unit tests target?") - let hasUITests = getBoolInput("Do you want to include UI tests target?") - let hasTestResources = if hasUnitTests || hasUITests { - getBoolInput("Do you need a TestResources target for test utilities and mocks?") - } else { - false - } - - let hasExample = hasUITests || getBoolInput("Do you want to create an Example target (for a demo app)?") - - print("\u{001B}[1m⏳ Generating...:\u{001B}[0m\n\n", terminator: " ") - - return ModuleInfo( - layer: layer, - moduleName: moduleName, - hasInterface: hasInterface, - hasTestResources: hasTestResources, - hasUnitTests: hasUnitTests, - hasUITests: hasUITests, - hasExample: hasExample, - hasLocalizedStrings: hasLocalizedStrings, - hasAssets: hasAssets - ) - } - - func scaffoldModule(_ moduleInfo: ModuleInfo) throws { - try scaffoldMainModule(moduleInfo) - - if moduleInfo.hasInterface { try scaffoldInterface(moduleInfo) } - if moduleInfo.hasTestResources { try scaffoldTestResources(moduleInfo) } - if moduleInfo.hasUnitTests { try scaffoldTests(moduleInfo) } - if moduleInfo.hasUITests { try scaffoldUITests(moduleInfo) } - if moduleInfo.hasExample { try scaffoldExample(moduleInfo) } - if moduleInfo.hasLocalizedStrings { try scaffoldLocalizedStrings(moduleInfo) } - if moduleInfo.hasAssets { try scaffoldAssets(moduleInfo) } - } - - func scaffoldMainModule(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Module", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - "--interface", moduleInfo.hasInterface.description, - "--testresources", moduleInfo.hasTestResources.description, - "--tests", moduleInfo.hasUnitTests.description, - "--uitests", moduleInfo.hasUITests.description, - "--example", moduleInfo.hasExample.description, - "--localizedstrings", moduleInfo.hasLocalizedStrings.description, - "--assets", moduleInfo.hasAssets.description, - ] - - try runTuistCommand(args) - } - - func scaffoldInterface(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Interface", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldTestResources(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "TestResources", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldTests(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Tests", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldUITests(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "UITests", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldExample(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Example", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldLocalizedStrings(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Localizable", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func scaffoldAssets(_ moduleInfo: ModuleInfo) throws { - let args = [ - "scaffold", - "Assets", - "--layer", moduleInfo.layer.rawValue, - "--name", moduleInfo.moduleName, - ] - - try runTuistCommand(args) - } - - func registerDependency(_ moduleInfo: ModuleInfo) throws { - let dependencyFilePath = "\(currentPath)Tuist/ProjectDescriptionHelpers/Dependencies/Dependency+Module.swift" - - guard var content = try? String(contentsOfFile: dependencyFilePath, encoding: .utf8) else { - throw ModuleGenerationError.failedToReadDependencyFile - } - - let newDependencies = createDependencyDeclarations(for: moduleInfo) - - guard let range = content.range(of: "public extension TargetDependency.Module.\(moduleInfo.layer.rawValue) {") else { - throw ModuleGenerationError.invalidInsertionPoint - } - - let insertionPoint = content.index(after: range.upperBound) - content.insert(contentsOf: " \(newDependencies.joined(separator: "\n "))\n", at: insertionPoint) - - let formattedContent = formatDependencyExtension(content, for: moduleInfo.layer) - - do { - try formattedContent.write(toFile: dependencyFilePath, atomically: true, encoding: .utf8) - print("✅ Dependency added to Dependency+Module.swift") - } catch { - throw ModuleGenerationError.failedToWriteDependencyFile - } - } - - func createDependencyDeclarations(for moduleInfo: ModuleInfo) -> [String] { - let moduleLowercaseName = moduleInfo.moduleName.prefix(1).lowercased() + moduleInfo.moduleName.dropFirst() - var declarations = [ - "static let \(moduleLowercaseName) = TargetDependency.\(moduleInfo.layer.rawValue.lowercased())(name: \"\(moduleInfo.moduleName)\")", - ] - - if moduleInfo.hasInterface { - declarations.append( - "static let \(moduleLowercaseName)Interface = TargetDependency.\(moduleInfo.layer.rawValue.lowercased())(name: \"\(moduleInfo.moduleName)\", targetType: .interface)" - ) - } - - if moduleInfo.hasTestResources { - declarations.append( - "static let \(moduleLowercaseName)TestResources = TargetDependency.\(moduleInfo.layer.rawValue.lowercased())(name: \"\(moduleInfo.moduleName)\", targetType: .testResources)" - ) - } - - return declarations - } - - /// Formats the dependency extension in the Dependency+Module.swift file. - /// - /// This method is responsible for organizing and formatting the dependency declarations - /// within a specific layer's extension in the Dependency+Module.swift file. It performs - /// the following tasks: - /// - /// 1. Identifies the correct extension block for the given layer. - /// 2. Sorts the dependency declarations alphabetically within the extension. - /// 3. Ensures proper indentation and spacing of the declarations. - func formatDependencyExtension(_ content: String, for layer: LayerType) -> String { - let lines = content.components(separatedBy: .newlines) - var formattedLines: [String] = [] - var inTargetExtension = false - var extensionLines: [String] = [] - var commentLine: String? - var emptyLineAdded = false - - for line in lines { - if line.contains("public extension TargetDependency.Module.\(layer.rawValue)") { - inTargetExtension = true - formattedLines.append(line) - } else if inTargetExtension { - if line.contains("}") { - // Add comment and empty line if they exist - if let comment = commentLine { - formattedLines.append(" " + comment) - } - if !emptyLineAdded { - formattedLines.append("") - } - // Sort and format the extension lines - extensionLines.sort() - formattedLines.append(contentsOf: extensionLines.map { " \($0)" }) - formattedLines.append("}") - inTargetExtension = false - } else if line.trimmingCharacters(in: .whitespaces).starts(with: "//") { - commentLine = line.trimmingCharacters(in: .whitespaces) - } else if line.trimmingCharacters(in: .whitespaces).isEmpty { - emptyLineAdded = true - } else if !line.trimmingCharacters(in: .whitespaces).isEmpty { - // Split multiple declarations on the same line - let declarations = line.components(separatedBy: "static let") - .filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty } - .map { "static let " + $0.trimmingCharacters(in: .whitespaces) } - extensionLines.append(contentsOf: declarations) - } - } else { - formattedLines.append(line) - } - } - - return formattedLines.joined(separator: "\n") - } - - func listCreatedTargets(_ moduleInfo: ModuleInfo) -> String { - var targets = [moduleInfo.moduleName] - - if moduleInfo.hasInterface { - targets.append("\(moduleInfo.moduleName)Interface") - } - if moduleInfo.hasTestResources { - targets.append("\(moduleInfo.moduleName)TestResources") - } - if moduleInfo.hasUnitTests { - targets.append("\(moduleInfo.moduleName)Tests") - } - if moduleInfo.hasUITests { - targets.append("\(moduleInfo.moduleName)UITests") - } - if moduleInfo.hasExample { - targets.append("\(moduleInfo.moduleName)Example") - } - - return targets.joined(separator: ", ") - } - - func runTuistCommand(_ args: [String]) throws { - let process = Process() - process.launchPath = "/usr/bin/env" - process.arguments = ["tuist"] + args - - let pipe = Pipe() - process.standardOutput = pipe - process.standardError = pipe - - do { - try process.run() - process.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - guard let output = String(data: data, encoding: .utf8) else { - throw ModuleGenerationError.failedToReadTuistOutput - } - print(output) - - if process.terminationStatus != 0 { - throw NSError( - domain: "TuistError", - code: Int(process.terminationStatus), - userInfo: [NSLocalizedDescriptionKey: "Tuist command failed"] - ) - } - } catch { - print("Failed to run Tuist command: \(error)") - throw error - } - } - - func getIntInput(min: Int, max: Int) -> Int { - while true { - print("\u{001B}[1mEnter a number between \(min) and \(max):\u{001B}[0m", terminator: " ") - if let input = readLine(), let number = Int(input), number >= min, number <= max { - return number - } - print("Invalid input. Please try again.") - } - } - - func getBoolInput(_ question: String) -> Bool { - while true { - print("\u{001B}[1m\(question)\u{001B}[0m (y/n):", terminator: " ") - if let input = readLine()?.lowercased() { - switch input { - case "y", "yes": - return true - case "n", "no": - return false - default: - break - } - } - print("Invalid input. Please enter 'y' or 'n'.") - } - } -} - -// swiftlint:enable disable_print - -// MARK: - Main Execution - -let moduleGenerator = ModuleGenerator() -moduleGenerator.generateModule() diff --git a/scripts/git-format-staged b/scripts/git-format-staged deleted file mode 100755 index 6644a57..0000000 --- a/scripts/git-format-staged +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python3 -# -# Git command to transform staged files according to a command that accepts file -# content on stdin and produces output on stdout. This command is useful in -# combination with `git add -p` which allows you to stage specific changes in -# a file. This command runs a formatter on the file with staged changes while -# ignoring unstaged changes. -# -# Usage: git-format-staged [OPTION]... [FILE]... -# Example: git-format-staged --formatter 'prettier --stdin-filepath "{}"' '*.js' -# -# Tested with Python versions 3.8 - 3.13. -# -# Original author: Jesse Hallett -# -# Source: https://github.com/hallettj/git-format-staged -# Reason for copying this is that the script can be installed using npm but we don't want that - -from __future__ import print_function -import argparse -from fnmatch import fnmatch -from gettext import gettext as _ -import os -import re -import subprocess -import sys - -# The string $VERSION is replaced during the publish process. -VERSION = '3.1.1' -PROG = sys.argv[0] - -def info(msg): - print(msg, file=sys.stdout) - -def warn(msg): - print('{}: warning: {}'.format(PROG, msg), file=sys.stderr) - -def fatal(msg): - print('{}: error: {}'.format(PROG, msg), file=sys.stderr) - exit(1) - -def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True, verbose=False): - try: - output = subprocess.check_output([ - 'git', 'diff-index', - '--cached', - '--diff-filter=AM', # select only file additions and modifications - '--no-renames', - 'HEAD' - ]) - for line in output.splitlines(): - entry = parse_diff(line.decode('utf-8')) - entry_path = normalize_path(entry['src_path'], relative_to=git_root) - if entry['dst_mode'] == '120000': - # Do not process symlinks - continue - if not (matches_some_path(file_patterns, entry_path)): - continue - if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write, verbose=verbose): - info('Reformatted {} with {}'.format(entry['src_path'], formatter)) - except Exception as err: - fatal(str(err)) - -# Run formatter on file in the git index. Creates a new git object with the -# result, and replaces the content of the file in the index with that object. -# Returns hash of the new object if formatting produced any changes. -def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=True, verbose=False): - orig_hash = diff_entry['dst_hash'] - new_hash = format_object(formatter, orig_hash, diff_entry['src_path'], verbose=verbose) - - # If the new hash is the same then the formatter did not make any changes. - if not write or new_hash == orig_hash: - return None - - # If the content of the new object is empty then the formatter did not - # produce any output. We want to abort instead of replacing the file with an - # empty one. - if object_is_empty(new_hash): - return None - - replace_file_in_index(diff_entry, new_hash) - - if update_working_tree: - try: - patch_working_file(diff_entry['src_path'], orig_hash, new_hash) - except Exception as err: - # Errors patching working tree files are not fatal - warn(str(err)) - - return new_hash - -file_path_placeholder = re.compile(r'\{\}') - -# Run formatter on a git blob identified by its hash. Writes output to a new git -# blob, and returns the hash of the new blob. -def format_object(formatter, object_hash, file_path, verbose=False): - get_content = subprocess.Popen( - ['git', 'cat-file', '-p', object_hash], - stdout=subprocess.PIPE - ) - command = re.sub(file_path_placeholder, file_path, formatter) - if verbose: - info(command) - format_content = subprocess.Popen( - command, - shell=True, - stdin=get_content.stdout, - stdout=subprocess.PIPE - ) - write_object = subprocess.Popen( - ['git', 'hash-object', '-w', '--stdin'], - stdin=format_content.stdout, - stdout=subprocess.PIPE - ) - - get_content.stdout.close() - format_content.stdout.close() - - if get_content.wait() != 0: - raise ValueError('unable to read file content from object database: ' + object_hash) - - if format_content.wait() != 0: - raise Exception('formatter exited with non-zero status') # TODO: capture stderr from format command - - new_hash, err = write_object.communicate() - - if write_object.returncode != 0: - raise Exception('unable to write formatted content to object database') - - return new_hash.decode('utf-8').rstrip() - -def object_is_empty(object_hash): - get_content = subprocess.Popen( - ['git', 'cat-file', '-p', object_hash], - stdout=subprocess.PIPE - ) - content, err = get_content.communicate() - - if get_content.returncode != 0: - raise Exception('unable to verify content of formatted object') - - return not content - -def replace_file_in_index(diff_entry, new_object_hash): - subprocess.check_call(['git', 'update-index', - '--cacheinfo', '{},{},{}'.format( - diff_entry['dst_mode'], - new_object_hash, - diff_entry['src_path'] - )]) - -def patch_working_file(path, orig_object_hash, new_object_hash): - patch = subprocess.check_output( - ['git', 'diff', '--no-ext-diff', '--color=never', orig_object_hash, new_object_hash] - ) - - # Substitute object hashes in patch header with path to working tree file - patch_b = patch.replace(orig_object_hash.encode(), path.encode()).replace(new_object_hash.encode(), path.encode()) - - apply_patch = subprocess.Popen( - ['git', 'apply', '-'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - output, err = apply_patch.communicate(input=patch_b) - - if apply_patch.returncode != 0: - raise Exception('could not apply formatting changes to working tree file {}'.format(path)) - -# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path? -diff_pat = re.compile(r'^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$') - -# Parse output from `git diff-index` -def parse_diff(diff): - m = diff_pat.match(diff) - if not m: - raise ValueError('Failed to parse diff-index line: ' + diff) - return { - 'src_mode': unless_zeroed(m.group(1)), - 'dst_mode': unless_zeroed(m.group(2)), - 'src_hash': unless_zeroed(m.group(3)), - 'dst_hash': unless_zeroed(m.group(4)), - 'status': m.group(5), - 'score': int(m.group(6)) if m.group(6) else None, - 'src_path': m.group(7), - 'dst_path': m.group(8) - } - -zeroed_pat = re.compile(r'^0+$') - -# Returns the argument unless the argument is a string of zeroes, in which case -# returns `None` -def unless_zeroed(s): - return s if not zeroed_pat.match(s) else None - -def get_git_root(): - return subprocess.check_output( - ['git', 'rev-parse', '--show-toplevel'] - ).decode('utf-8').rstrip() - -def normalize_path(p, relative_to=None): - return os.path.abspath( - os.path.join(relative_to, p) if relative_to else p - ) - -def matches_some_path(patterns, target): - is_match = False - for signed_pattern in patterns: - (is_pattern_positive, pattern) = from_signed_pattern(signed_pattern) - if fnmatch(target, normalize_path(pattern)): - is_match = is_pattern_positive - return is_match - -# Checks for a '!' as the first character of a pattern, returns the rest of the -# pattern in a tuple. The tuple takes the form (is_pattern_positive, pattern). -# For example: -# from_signed_pattern('!pat') == (False, 'pat') -# from_signed_pattern('pat') == (True, 'pat') -def from_signed_pattern(pattern): - if pattern[0] == '!': - return (False, pattern[1:]) - else: - return (True, pattern) - -class CustomArgumentParser(argparse.ArgumentParser): - def parse_args(self, args=None, namespace=None): - args, argv = self.parse_known_args(args, namespace) - if argv: - msg = argparse._( - 'unrecognized arguments: %s. Do you need to quote your formatter command?' - ) - self.error(msg % ' '.join(argv)) - return args - -if __name__ == '__main__': - parser = CustomArgumentParser( - description='Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.', - epilog='Example: %(prog)s --formatter "prettier --stdin-filepath \'{}\'" "src/*.js" "test/*.js"' - ) - parser.add_argument( - '--formatter', '-f', - required=True, - help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin-filepath \'{}\'")' - ) - parser.add_argument( - '--no-update-working-tree', - action='store_true', - help='By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.' - ) - parser.add_argument( - '--no-write', - action='store_true', - help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename \'{}\' >&2" "*.js"' - ) - parser.add_argument( - '--version', - action='version', - version='%(prog)s version {}'.format(VERSION), - help='Display version of %(prog)s' - ) - parser.add_argument( - '--verbose', - help='Show the formatting commands that are running', - action='store_true' - ) - parser.add_argument( - 'files', - nargs='+', - help='Patterns that specify files to format. The formatter will only transform staged files that are given here. Patterns may be literal file paths, or globs which will be tested against staged file paths using Python\'s fnmatch function. For example "src/*.js" will match all files with a .js extension in src/ and its subdirectories. Patterns may be negated to exclude files using a "!" character. Patterns are evaluated left-to-right. (Example: "main.js" "src/*.js" "test/*.js" "!test/todo/*")' - ) - args = parser.parse_args() - files = vars(args)['files'] - format_staged_files( - file_patterns=files, - formatter=vars(args)['formatter'], - git_root=get_git_root(), - update_working_tree=not vars(args)['no_update_working_tree'], - write=not vars(args)['no_write'], - verbose=vars(args)['verbose'] - ) \ No newline at end of file diff --git a/scripts/rename.sh b/scripts/rename.sh deleted file mode 100755 index 79014fb..0000000 --- a/scripts/rename.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -original=STRVTemplate -# Used for Keychain -keysOriginal=strv_template -updated=`echo $1 | sed 's/ /_/g'` -updated_lowercase=`echo $updated | tr '[:upper:]' '[:lower:]'` - -echo "Starting renaming of STRVTemplate -> $1" - -# Replace strings in all files (except specific directories) -echo "Replacing STRVTemplate in all files..." -find . -type f -not -path "./.git/*" -not -path "./.githooks*" -not -path "./Pods*" -not -path "./scripts*" -not -path "./vendor*" -not -path "./.bundle*" -not -path "./Tuist/.build/*" -print0 | while IFS= read -r -d '' file; do - # First, copy the file with its permissions - cp "$file" "$file.tmp" - sed "s/$original/$updated/g; s/$keysOriginal/$updated_lowercase/g" "$file" 2> /dev/null 1> "$file.tmp" - size=`stat -f%z "$file.tmp"` - if [ "$size" = "0" ]; then - rm "$file.tmp" - else - rm "$file" - mv "$file.tmp" "$file" - fi -done - -# Rename directories -echo "Renaming directories..." -find . -type d -name "*$original*" -print0 | while IFS= read -r -d '' file; do - new=`echo "$file" | sed "s/$original/$updated/g"` - mv "$file" "$new" -done - -# Rename files -echo "Renaming files..." -find . -type f -name "*$original*" -print0 | while IFS= read -r -d '' file; do - new=`echo "$file" | sed "s/$original/$updated/g"` - mv "$file" "$new" -done - -echo "Renaming process completed." \ No newline at end of file diff --git a/scripts/setup_bundler.sh b/scripts/setup_bundler.sh deleted file mode 100755 index f783f37..0000000 --- a/scripts/setup_bundler.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -desired_bundler_version=$(cat .bundler-version) -installed_bundler_version=$(gem list bundler | grep -w $desired_bundler_version) - -if [ -z "$installed_bundler_version" ]; then - echo "Bundler version $desired_bundler_version is not installed. Installing now..." - gem install bundler -v $desired_bundler_version -else - echo "Found correct Bundler version: $desired_bundler_version" -fi \ No newline at end of file diff --git a/scripts/setup_ci.sh b/scripts/setup_ci.sh deleted file mode 100755 index 57535fc..0000000 --- a/scripts/setup_ci.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -if [ "$1" == "enterprise" ] || [ "$1" == "testflight" ] || [ "$1" == "integrations" ]; then - [[ $1 = "integrations" ]] && yml="$1.yml" || yml="$1-deployment.yml" - - mkdir -p .github/workflows - - # Copy environment file if it exists - if [ -f "fastlane/sample.env.$1" ]; then - cp "fastlane/sample.env.$1" "fastlane/.env.$1" - fi - - # Copy workflow file if it exists - if [ -f ".github/sample-workflows/$yml" ]; then - cp ".github/sample-workflows/$yml" ".github/workflows/$yml" - - if [ "$1" != "integrations" ]; then - echo "🟡 Configure .github/workflows/$yml and provide secrets via Github secrets if needed." - echo "If you want to run fastlane locally, make sure that fastlane/.env.$1 is .gitignored and contains all necessary secrets" - fi - else - if [ "$1" == "integrations" ]; then - echo "💡 Integrations workflows are already set up in .github/workflows/" - else - echo "⚠️ Warning: .github/sample-workflows/$yml not found" - fi - fi -else - echo "Provide a CI type: 'enterprise', 'testflight' or 'integrations'." -fi diff --git a/scripts/setup_tuist_githook.sh b/scripts/setup_tuist_githook.sh deleted file mode 100755 index b7c8055..0000000 --- a/scripts/setup_tuist_githook.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Script for generating project files from the Tuist definition files -script=" -# Tuist -if [ -f \"App/Project.swift\" ]; then - tuist generate --no-open -fi" - -# Destination file where the script should be set -hookFile=".githooks/post-checkout" - -if [ ! -f "$hookFile" ]; then - # post-checkout git hook file doesn't exist - create file, add header, and script - echo "#!/bin/bash" > "$hookFile" - echo "$script" >> "$hookFile" -elif ! grep -q "Tuist" "$hookFile"; then - # File exists but the Tuist section is missing - add just the script - echo "$script" >> "$hookFile" -fi - -# Hooks needs to be executable -chmod +x .githooks/post-checkout \ No newline at end of file diff --git a/scripts/swiftformat.sh b/scripts/swiftformat.sh deleted file mode 100755 index a467bb5..0000000 --- a/scripts/swiftformat.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Description: -# This script facilitates the running of SwiftFormat. -# It checks for the correct version of SwiftFormat, and runs SwiftFormat on git staged files using 'git-format-staged' binary. -# The desired version of SwiftFormat is specified in a '.swiftformat-version' file located in the project root directory. - -# Usage: -# This script is executed during pre-commit git hook in order to prevent the loss of Xcode's undo functionality - -# Exit Codes: -# - 0: SwiftFormat ran successfully or script exited early in a CI environment. -# - 1: An error occurred, such as SwiftFormat not being installed or version mismatch. - -if [ $CI ]; then - echo "No need to run SwiftFormat on CI" - exit 0 -fi - -cd "$(dirname "$0")/.." - -eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" - -swiftformat_bin=$(mise which swiftformat) - -if ! mise which swiftformat >/dev/null; then - echo "error: SwiftFormat is not installed. Install by running setup.swift install or setup.swift install-dependencies" - exit 1 -fi - -desired_version=$(mise current swiftformat) -current_version=$(mise exec -- swiftformat --version) - -if [[ $current_version != $desired_version ]]; then - echo "error: SwiftFormat version mismatch. Expected $desired_version but found $current_version." - echo "error: Install the correct version by running setup.swift install" - exit 1 -fi - -# Run SwiftFormat on git-staged files -cd "$(git rev-parse --show-toplevel)" - -./scripts/git-format-staged --formatter "$swiftformat_bin stdin --stdinpath '{}'" "*.swift" \ No newline at end of file diff --git a/scripts/swiftlint.sh b/scripts/swiftlint.sh deleted file mode 100755 index 3a26f40..0000000 --- a/scripts/swiftlint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Description: -# This script facilitates the running of SwiftLint. -# It checks for the correct version of SwiftLint, and runs SwiftLint. -# The desired version of SwiftLint is fetched directly via mise. - -# Usage: -# This script is run as a pre-build phase script. -# In a CI environment, SwiftLint will be run by Danger instead. - -# Exit Codes: -# - 0: SwiftLint ran successfully or script exited early in a CI environment. -# - 1: An error occurred, such as SwiftLint not being installed or version mismatch. - -if [ $CI ]; then - echo "No need to run SwiftLint, Danger will run it!" - exit 0 -fi - -cd "$(dirname "$0")/.." - -# Activate Mise so that Xcode can find it (https://mise.jdx.dev/ide-integration.html#xcode) -eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" - -if ! mise which swiftlint >/dev/null; then - echo "error: SwiftLint is not installed. Install by running setup.swift install or setup.swift install-dependencies" - exit 1 -fi - -desired_version=$(mise current swiftlint) -current_version=$(mise exec -- swiftlint --version) - -if [[ $current_version != $desired_version ]]; then - echo "error: SwiftLint version mismatch. Expected $desired_version but found $current_version." - echo "error: Install the correct version by running setup.swift install" - exit 1 -fi - -# Run SwiftLint -mise exec -- swiftlint \ No newline at end of file diff --git a/scripts/update_dependencies.rb b/scripts/update_dependencies.rb deleted file mode 100644 index 84eaa48..0000000 --- a/scripts/update_dependencies.rb +++ /dev/null @@ -1,128 +0,0 @@ -# update_dependencies.rb -# -# Description: -# This script updates the versions of our development tools and dependencies. -# It currently supports SwiftLint, SwiftFormat, Tuist, and Cocoapods if specified in a Gemfile. -# -# Usage: -# This script is run via the `swift setup.swift update-tools` -# Ensure Gemfile is present if updating Cocoapods version. -# -# Note: -# Cocoapods are updated via the `setup.swift`` but Swiftlint + SwiftFormat and deliberately updated -# via their specific scripts to limit the number of requests that need to be done to Github. -# There is a limit rate to unauthorized requests per IP address of 60/hour. - -require 'json' -require 'net/http' -require 'uri' - -# Definitions -def get_current_version(tool_name) - # Fetch current version using mise current - `mise current #{tool_name}`.strip -end - -def get_latest_version(tool_name) - # Fetch latest version using mise latest - `mise latest #{tool_name}`.strip -end - -def success_message(tool, current_version, latest_version) - "🟢 Updated #{tool} from #{current_version} to #{latest_version}" -end - -def no_update_message(tool, current_version) - "🟡 No update needed for #{tool}. Current version matches the latest version: #{current_version}" -end - -def failure_message(tool, error_message) - "🔴 Failed to update #{tool}. #{error_message}" -end - -def gemfile_contains_cocoapods?(gemfile_content) - !!gemfile_content.match(/gem 'cocoapods'/) -end - -def update_gemfile_with_cocoapods_version(cocoapods_version, gemfile_content) - updated_content = gemfile_content.gsub(/gem 'cocoapods', '.*'/, "gem 'cocoapods', '#{cocoapods_version}'") - File.write('Gemfile', updated_content) -end - -def update_cocoapods_version(gemfile_content) - if gemfile_contains_cocoapods?(gemfile_content) - current_cocoapods_version_match = gemfile_content.match(/gem 'cocoapods', '(.*?)'/) - - if current_cocoapods_version_match - current_cocoapods_version = current_cocoapods_version_match[1] - latest_cocoapods_version = `gem info cocoapods --remote | grep '^cocoapods' | awk '{print $2}' | tr -d '()'`.strip - - if latest_cocoapods_version.empty? - puts failure_message('Cocoapods', 'Unable to fetch latest version.') - elsif current_cocoapods_version != latest_cocoapods_version - update_gemfile_with_cocoapods_version(latest_cocoapods_version, gemfile_content) - puts success_message('Cocoapods', current_cocoapods_version, latest_cocoapods_version) - - # Update Cocoapods by installing the bundle - system('swift setup.swift install-bundle') - system('bundle exec pod install') - else - puts no_update_message('Cocoapods', current_cocoapods_version) - end - else - puts 'Error parsing current Cocoapods version in Gemfile.' - end - else - puts "Gemfile doesn't contain Cocoapods" - end -end - -def update_mise_self - puts "Running Mise self-update..." - system('mise self-update') -end - -# Update Mise -update_mise_self - -# Tools -tools = ['swiftlint', 'swiftformat', 'tuist'] - -# Update each of the tools specified above -tools.each do |mise_key| - begin - # Fetch current and latest versions using mise commands - current_version = get_current_version(mise_key) - latest_version = get_latest_version(mise_key) - - if latest_version.empty? - puts failure_message(mise_key.capitalize, 'Unable to fetch the latest version using mise latest.') - next - end - - if current_version != latest_version - # Use mise use to set the tool to the latest version - system("mise use #{mise_key}@#{latest_version}") - - # Success message - puts success_message(mise_key.capitalize, current_version, latest_version) - else - # No update needed message - puts no_update_message(mise_key.capitalize, current_version) - end - rescue StandardError => e - puts failure_message(mise_key.capitalize, e.message) - end -end - -# Update Cocoapods if found in Gemfile -if File.exist?('Gemfile') - begin - gemfile_content = File.read('Gemfile') - update_cocoapods_version(gemfile_content) - rescue StandardError => e - puts failure_message('Cocoapods', e.message) - end -else - puts 'Gemfile does not exist.' -end \ No newline at end of file diff --git a/scripts/verify_ruby.sh b/scripts/verify_ruby.sh deleted file mode 100755 index 24dae9f..0000000 --- a/scripts/verify_ruby.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash - -# Description: -# This script ensures, the recommended Ruby version specified in a .ruby-version file, is installed and active for this repository. -# If the required version is not found, the script attempts to install it using the detected Ruby version manager (rbenv, rvm, or chruby). - -recommended_ruby_version=$(cat .ruby-version) -# Attempt to get the current Ruby version, capturing any error output -current_ruby_version=$(ruby -e 'puts RUBY_VERSION' 2>&1) - -# Function to check which Ruby version manager is in use -detect_ruby_manager() { - if command -v rbenv >/dev/null 2>&1; then - echo "rbenv" - elif command -v rvm >/dev/null 2>&1; then - echo "rvm" - elif command -v chruby >/dev/null 2>&1; then - echo "chruby" - else - echo "none" - fi -} - -install_ruby_version() { - local version=$1 - local manager=$2 - - case $manager in - rbenv) - echo "Attempting to install Ruby $version using rbenv..." - brew update && brew upgrade rbenv ruby-build # make sure that we have latest ruby-build - rbenv install "$version" && rbenv local "$version" - ;; - rvm) - echo "Attempting to install Ruby $version using rvm..." - rvm install "$version" && rvm use "$version" - ;; - chruby) - echo "Attempting to install Ruby $version using ruby-install..." - ruby-install ruby-"$version" && chruby ruby-"$version" - ;; - *) - echo "Unable to install Ruby $version. Unsupported Ruby version manager." - return 1 - ;; - esac -} - -# Function to output a warning message and instructions based on the Ruby manager -output_warning() { - local recommended_ruby_version=$1 - local ruby_manager=$2 - - echo "⚠️ Recommended Ruby version $recommended_ruby_version is not installed." - - # ANSI escape code to start bold and underline text - local bold_underline="\033[1;4m" - - # ANSI escape code to reset text formatting - local reset="\033[0m" - - case $ruby_manager in - rbenv) - echo -e "Please install it using rbenv with the following command:" - echo -e "${bold_underline}rbenv install $recommended_ruby_version${reset}" - ;; - rvm) - echo -e "Please install it using rvm with the following command:" - echo -e "${bold_underline}rvm install $recommended_ruby_version${reset}" - ;; - chruby) - echo -e "Please install it using ruby-install with the following command:" - echo -e "${bold_underline}ruby-install ruby-$recommended_ruby_version${reset}" - ;; - *) - echo "Please install it using your Ruby version manager." - ;; - esac -} - -# If the current Ruby version matches the recommended version, we're done -if [[ "$current_ruby_version" == "$recommended_ruby_version" ]]; then - echo "🟢 Found recommended Ruby version: $recommended_ruby_version" - exit 0 -fi - -ruby_manager=$(detect_ruby_manager) - -# Attempt to install the recommended Ruby version -install_ruby_version "$recommended_ruby_version" "$ruby_manager" -install_result=$? - -# Check the Ruby version again after attempting to install -current_ruby_version=$(ruby -e 'puts RUBY_VERSION' 2>&1) - -if [[ "$current_ruby_version" == "$recommended_ruby_version" ]]; then - echo "🟢 Successfully installed and switched to recommended Ruby version: $recommended_ruby_version" - exit 0 -elif [[ $install_result -eq 0 ]]; then - echo "⚠️ Installed Ruby $recommended_ruby_version, but failed to switch to it. Please check your Ruby version manager." - exit 1 -else - output_warning "$recommended_ruby_version" "$ruby_manager" - exit 1 -fi \ No newline at end of file diff --git a/setup.swift b/setup.swift deleted file mode 100755 index da4225e..0000000 --- a/setup.swift +++ /dev/null @@ -1,761 +0,0 @@ -#!/usr/bin/env swift -import Foundation - -// swiftlint:disable all - -let shell = Shell() -// This enables the script to exit when the user presses Ctrl+C -signal(SIGINT) { _ in - Task { @MainActor in - shell.terminateCurrentTask() - exit(0) - } -} - -enum Command: String, CaseIterable { - case help - case setup - case install - case installBundle = "install-bundle" - case installDependencies = "install-dependencies" - case updateDependencies = "update-dependencies" - case setupTuist = "setup-tuist" - case setupHooks = "setup-hooks" - case renameProject = "rename-project" - case setupCI = "setup-ci" - case removeTuistFiles = "remove-tuist-files" - case setupVersionIcon = "install-version-icon" -} - -extension Command { - var description: String { - switch self { - case .help: - "Prints this manual." - - case .setup: - "Runs the following commands: setupCI -> renameProject -> setupHooks -> installMiseDependencies -> installBundle -> setupTuist (if needed) -> setupVersionIcon." - - case .install: - "Runs the following commands: setupHooks -> installMiseDependencies -> installBundle -> installTuistDependencies -> installVersionIcon (if needed)." - - case .installBundle: - "We use Gemfile to ensure that you have all dependencies in correct versions. `install-bundle` installs all dependencies to the project directory." - - case .installDependencies: - "Installs Mise and Tuist dependencies (Tuist, SwiftLint, SwiftFormat) if needed." - - case .updateDependencies: - "Updates dependencies (Tuist, SwiftLint, SwiftFormat) to the latest versions" - - case .setupTuist: - "Installs Tuist environment, installs dependencies and generates the project files, adds appropriate files to the gitignore, removes gitignored files from git and setups git hooks." - - case .setupHooks: - "Creates symbolic links for hooks in githooks to the .git/hooks dictionary." - - case .renameProject: - "If you want to rename the project from `STRVTemplate` to something different (and you probably want that), just run the following command. You'll be asked for the name." - - case .setupCI: - "Copies .env and workflow files as needed." - - case .removeTuistFiles: - "Removes Tuist definition files." - - case .setupVersionIcon: - "VersionIcon is optional feature. It creates an app icon overlay with the ribbon and/or version (build) text based on the app configuration. VersionIcon is added as script build phase. It is STRV internal project and it is highly customizable." - } - } -} - -enum SetupError: LocalizedError { - case fileOperationFailed(String? = nil) - case emptyProjectName - case missingWorkspace - case miseNotInstalled(command: String) - case miseNotActivated(command: String) - - var errorDescription: String? { - switch self { - case let .fileOperationFailed(error?): - "file operation failed: \(error)" - case .fileOperationFailed(.none): - "file operation failed" - case .emptyProjectName: - "project name can't be empty" - case .missingWorkspace: - "no workspace in current directory" - case let .miseNotInstalled(command): - "Please install Mise (https://mise.jdx.dev/getting-started.html#_1-install-mise-cli) and run the `\(command)` command again." - case let .miseNotActivated(command): - "Please activate Mise (https://mise.jdx.dev/getting-started.html#_2a-activate-mise) and run the `\(command)` command again." - } - } -} - -@MainActor -final class Main { - private let commandRunner = CommandRunner() - - func run() async { - let argumentCommand = CommandLine.argc > 1 ? CommandLine.arguments[1] : nil - - if let argumentCommand { - if let command = Command(rawValue: argumentCommand) { - await commandRunner.run(command) - } else { - print("⚠️ Error: unknown command") - exit(1) - } - } else { - await askForCommand() - } - } -} - -private extension Main { - func askForCommand() async { - printCommands() - print("\n") - if let commandNumber = askForCommandNumber(), - Command.allCases.indices.contains(commandNumber) - { - let command = Command.allCases[commandNumber] - print(" Next time you can do this by directly typing `./setup.swift \(command.rawValue)`") - await commandRunner.run(command) - } else { - print("⚠️ Error: unknown command") - exit(1) - } - } - - func printCommands() { - for (index, command) in Command.allCases.enumerated() { - print("\(index): \(command.rawValue)") - } - } - - func askForCommandNumber() -> Int? { - print("\n💬 Which number would you like to run?") - return readLine().map(Int.init) ?? nil - } -} - -@MainActor -final class CommandRunner { - func run(_ command: Command) async { - do { - switch command { - case .help: - printManual() - - case .installBundle: - try await installBundle(shouldVerifyMise: true) - - case .installDependencies: - let tuistDefinitionFileExists = await tuistDefinitionFileExists() - try await installMiseDependencies(shouldVerifyMise: true) - try await installTuistDependencies(isUsingTuist: tuistDefinitionFileExists) - - case .updateDependencies: - await updateDependencies() - - case .setupTuist: - try await verifyMiseIsInstalled(command: command) - try await setupTuist() - - case .setupHooks: - try setupHooks() - - case .renameProject: - try await renameProject() - - case .setupCI: - await setupCI() - - case .setup: - try await setup() - - case .install: - try await install() - - case .removeTuistFiles: - await removeTuistFiles() - - case .setupVersionIcon: - let tuistDefinitionFileExists = await tuistDefinitionFileExists() - if tuistDefinitionFileExists { - try? await setupVersionIcon(projectName: nil) - try await installTuistDependencies(isUsingTuist: tuistDefinitionFileExists) - } else { - print("⚠️ Error: VersionIcon requires Tuist and it is not installed. Please run `./setup.swift setup-tuist` first.") - exit(1) - } - } - } catch { - print("⚠️ Error: \(error.localizedDescription)") - exit(1) - } - } -} - -private extension CommandRunner { - var isCI: Bool { - guard let ciValue = ProcessInfo.processInfo.environment["CI"] else { - return false - } - return ciValue == "true" || ciValue == "1" - } - - var homeDirectory: String { - FileManager.default.homeDirectoryForCurrentUser.path - } - - func printManual() { - print("\n🔵 Available commands:\n") - for (index, command) in Command.allCases.enumerated() { - print("\(index): \(command.rawValue)") - print("// \(command.description)\n") - } - } - - func setup() async throws { - try await verifyMiseIsInstalled(command: .setup) - await setupCI(offerSkip: true) - let projectName = try await renameProject() - try setupHooks() - try await installMiseDependencies(shouldVerifyMise: false) - try await installBundle(shouldVerifyMise: false) - try await setupVersionIcon(projectName: projectName) - try await setupTuist() - } - - func install() async throws { - try await verifyMiseIsInstalled(command: .install) - try setupHooks() - let tuistDefinitionFileExists = await tuistDefinitionFileExists() - try await installMiseDependencies(shouldVerifyMise: false) - try await installBundle(shouldVerifyMise: false) - try await installTuistDependencies(isUsingTuist: tuistDefinitionFileExists) - } - - func installBundle(shouldVerifyMise: Bool) async throws { - if shouldVerifyMise { - try await verifyMiseIsInstalled(command: .installBundle) - } - - print("🔵 Install bundle: \(bundlerVersion)") - await shell.runAndExitIfFailure("./scripts/setup_bundler.sh") - await shell.runAndExitIfFailure("bundle _\(bundlerVersion)_ install") - } - - func installMiseDependencies(shouldVerifyMise: Bool) async throws { - if shouldVerifyMise { - try await verifyMiseIsInstalled(command: .installDependencies) - } - - print("🔵 Install Mise dependencies") - - await shell.runAndExitIfFailure("mise install -y") - } - - func installTuistDependencies(isUsingTuist: Bool) async throws { - print("🔵 Install Tuist dependencies") - - if isUsingTuist { - // It seems we need to use `mise exec` because after the initial Mise installation the $PATH doesn't correctly update - // not even via calling `eval "$(mise activate zsh --shims)"` so Mise is unable to find the correct tool (in this case tuist) - // Every other setup run works correctly without calling `mise exec` because the $PATH updating is the working fine. - // But for the initial "edge-case" it seems we need to keep the `mise exec`. - await shell.runAndExitIfFailure("mise exec -- tuist install") - await shell.runAndExitIfFailure("mise exec -- tuist generate --no-open") - } - } - - func updateDependencies() async { - await shell.runAndExitIfFailure("ruby scripts/update_dependencies.rb") - } - - func setupTuist() async throws { - print("🔵 Setup Tuist") - guard await tuistDefinitionFileExists() else { - print("Tuist Project.swift definition file is missing.") - return - } - try await installTuistDependencies(isUsingTuist: true) - - await shell.runAndExitIfFailure("./scripts/setup_tuist_githook.sh") - try setupHooks() - } - - func setupHooks() throws { - guard !isCI else { - return - } - - print("🔵 Setup hooks") - try installHooks() - } - - func installHooks() throws { - let fileManager = FileManager.default - let currentDirectoryPath = fileManager.currentDirectoryPath - let hooksDirectoryPath = currentDirectoryPath + "/.git/hooks" - let sourceDirectoryPath = currentDirectoryPath + "/.githooks" - - if !fileManager.fileExists(atPath: hooksDirectoryPath) { - print("🟡 Creating missing .git/hooks/ directory") - try fileManager.createDirectory(atPath: hooksDirectoryPath, withIntermediateDirectories: true, attributes: nil) - } - - let hooks = try fileManager.contentsOfDirectory(atPath: sourceDirectoryPath) - .filter { !$0.hasPrefix(".") && URL(fileURLWithPath: $0).pathExtension.isEmpty } - - for hook in hooks { - let target = currentDirectoryPath + "/.git/hooks/\(hook)" - let source = currentDirectoryPath + "/.githooks/\(hook)" - let backup = "\(target).bak" - - guard fileManager.fileExists(atPath: source) else { - print("🟡 Skipping \(hook) hook because it doesn't exist at expected path: \(source)") - continue - } - - if - let destination = try? fileManager.destinationOfSymbolicLink(atPath: target), - destination == source - { - print("🟢 \(hook) hook is already installed") - continue - } - - if fileManager.contentsEqual(atPath: target, andPath: source) { - print("🟢 \(hook) hook is already installed") - continue - } - - if fileManager.fileExists(atPath: target) { - print("🟡 Found an existing hook for \(hook). Creating a backup at \(backup)") - do { - if fileManager.fileExists(atPath: backup) { - try fileManager.removeItem(atPath: backup) - } - try fileManager.moveItem(atPath: target, toPath: backup) - } catch { - throw SetupError.fileOperationFailed("Backup creation failed: \(error.localizedDescription)") - } - } - - do { - try fileManager.createSymbolicLink(atPath: target, withDestinationPath: source) - } catch { - throw SetupError.fileOperationFailed("\(hook) Symlink creation failed: \(error.localizedDescription)") - } - print("🟢 Installed \(hook) hook") - } - } - - @discardableResult - func renameProject() async throws -> String { - print("\n🔵 Rename project") - print("💬 New project name:") - guard let projectName = readLine(), !projectName.isEmpty else { - throw SetupError.emptyProjectName - } - print("🔵 Renaming project... (can take a minute)") - await shell.runAndExitIfFailure("./scripts/rename.sh \"\(projectName)\"") - print("🟢 Renamed to \(projectName)") - return projectName - } - - func setupCI(offerSkip: Bool = false) async { - print("🔵 Setup CI") - - print("🔵 Setting up default Integrations workflow") - await shell.runAndExitIfFailure("./scripts/setup_ci.sh integrations") - - let availableWorkflows = [ - "testflight", - "enterprise", - ] - let workflowsString = availableWorkflows - .enumerated() - .map { "\($0): \($1)" } - .joined(separator: " | ") - print("\n🔵 Available CI workflows: \(workflowsString)") - print("💬 Which workflow number would you like to select?" + (offerSkip ? " Just press enter to skip." : "")) - if let workflowNumber = readLine().map(Int.init) ?? nil, - availableWorkflows.indices.contains(workflowNumber) - { - let selectedWorkflow = availableWorkflows[workflowNumber] - await shell.runAndExitIfFailure("./scripts/setup_ci.sh \"\(selectedWorkflow)\"") - } else { - print("🔵 Skipped") - } - } - - func removeTuistFiles() async { - print("🔵 Remove Tuist files") - await shell.runAndExitIfFailure("rm -rf Tuist App/Project.swift Workspace.swift .tuist-version .githooks/post-checkout .package.resolved Derived") - } - - func tuistDefinitionFileExists() async -> Bool { - await shell.run("test -e App/Project.swift") == 0 - } - - func shouldSetupVersionIcon() -> Bool { - print("\n💬 Do you want to setup VersionIcon script (https://github.com/strvcom/ios-version-icon) ? (y/n)") - let answer = readLine() - if answer == "y" { - return true - } else if answer == "n" { - return false - } else { - print("⚠️ Unknown answer") - exit(1) - } - } - - func setupVersionIcon(projectName: String?) async throws { - // Test whether Tuist is installed - var isdirectory : ObjCBool = true - guard FileManager.default.fileExists(atPath: "Tuist", isDirectory: &isdirectory) else { - print("⚠️ Tuist is not installed") - return - } - - guard shouldSetupVersionIcon() else { - return - } - - var usedProjectName: String - if let projectName { - usedProjectName = projectName - } else { - let currentDirectory = FileManager.default.currentDirectoryPath - let enumerator = FileManager.default.enumerator(atPath: currentDirectory) - let filePaths = enumerator?.allObjects as! [String] - let workspaces = filePaths.filter { $0.contains(".xcworkspace") } - - usedProjectName = try getProjectName(from: workspaces) - } - try setupVersionIconGlobalConstants() - try setupVersionIconUpdateGitIgnore(projectName: usedProjectName) - await setupVersionIconRemoveIconsFromGit(projectName: usedProjectName) - } - - /// Gets the name for project - from the workspace file or by asking the user - func getProjectName(from workspaces: [String]) throws -> String { - // Handle the case that there are many workspace files in the folder - if workspaces.count > 1 { - // Ask the user - print("\n💬 Project name: ") - guard let projectName = readLine(), !projectName.isEmpty else { - throw SetupError.emptyProjectName - } - return projectName - } - // Otherwise derive the project name from single workspace file - else { - guard let workspace = workspaces.first else { - throw SetupError.missingWorkspace - } - - return workspace.replacingOccurrences(of: ".xcworkspace", with: "") - } - } - - /// Change Tuist GlobalConstants.swift - func setupVersionIconGlobalConstants() throws { - print("🔵 Modifying Tuist GlobalConstants") - var globalConstantsContents = try String(contentsOfFile: globalConstantsPath, encoding: .utf8) - globalConstantsContents = globalConstantsContents.replacing("useVersionIcon = false", with: "useVersionIcon = true") - try globalConstantsContents.write(toFile: globalConstantsPath, atomically: true, encoding: .utf8) - } - - /// Update .gitignore for VersionIcon - func setupVersionIconUpdateGitIgnore(projectName: String) throws { - print("🔵 Updating .gitignore") - let gitIgnore = ".gitignore" - var gitIgnoreContents = try String(contentsOfFile: gitIgnore) - - if gitIgnoreContents.contains("AppIcon.appiconset") { - print("VersionIcon is already set in .gitignore") - } else { - gitIgnoreContents.append( - """ - - # VersionIcon - - App/{{AppName}}/Application/Resources/Assets.xcassets/AppIcon.appiconset/* - """ - .replacing("{{AppName}}", with: projectName) - ) - try gitIgnoreContents.write(toFile: gitIgnore, atomically: true, encoding: .utf8) - } - } - - /// Remove icon files from git. We want to avoid commiting the changed icons with every commit. - /// AppIcon is now cosidered as only local and AppIconOriginal is shared image source. - func setupVersionIconRemoveIconsFromGit(projectName: String) async { - print("🔵 Removing AppIcon images from git") - await shell.runAndExitIfFailure("git rm -f \"App/\(projectName)/Application/Resources/Assets.xcassets/AppIcon.appiconset/*\"") - } - - /// Installs VersionIcon if needed. - /// `AppIcon` is git ignored, but Xcode still needs it to build the app. - /// We copy the original icon to `AppIcon.appiconset` if it doesn't exist. - /// This usually happens when existing project is cloned and the icon is not present. - func installVersionIconIfNeeded() throws { - guard try isVersionIconSetup() else { - print("🔵 VersionIcon is not set up. Skipping.") - return - } - - guard let projectName = try getGlobalConstantsProjectName() else { - print("🔴 Could not determine project name from GlobalConstants.swift. Please ensure it is set correctly.") - return - } - let originalAppIconPath = "App/\(projectName)/Application/Resources/Assets.xcassets/AppIconOriginal.appiconset" - let appIconPath = "App/\(projectName)/Application/Resources/Assets.xcassets/AppIcon.appiconset" - - guard FileManager.default.fileExists(atPath: originalAppIconPath) else { - print("🔴 Original AppIcon not found at \(originalAppIconPath). Please ensure it exists.") - return - } - - guard !FileManager.default.fileExists(atPath: appIconPath) else { - print("🟢 AppIcon is already set up.") - return - } - - try FileManager.default.copyItem(atPath: originalAppIconPath, toPath: appIconPath) - print("🟢 Copied original AppIcon to \(appIconPath)") - } - - func isVersionIconSetup() throws -> Bool { - let contents = try String(contentsOfFile: globalConstantsPath, encoding: .utf8) - return contents.contains("useVersionIcon = true") - } - - var bundlerVersion: String { - let fileName = ".bundler-version" - let fileURL = URL(filePath: fileName) - do { - return try String(contentsOf: fileURL, encoding: .utf8) - } catch { - print("⚠️ Error: \(String(describing: error))") - exit(1) - } - } - - func getGlobalConstantsProjectName() throws -> String? { - let globalConstantsContents = try String(contentsOfFile: globalConstantsPath, encoding: .utf8) - let pattern = #".*projectName\s*=\s*"([^"]+)""# - let regex = try NSRegularExpression(pattern: pattern, options: []) - - if let match = regex.firstMatch(in: globalConstantsContents, options: [], range: NSRange(globalConstantsContents.startIndex..., in: globalConstantsContents)), - let range = Range(match.range(at: 1), in: globalConstantsContents) - { - let projectName = String(globalConstantsContents[range]) - return projectName - } - - return nil - } - - var globalConstantsPath: String { - "./Tuist/ProjectDescriptionHelpers/GlobalConstants.swift" - } - - func promptWithDefault(message: String, defaultValue: String = "y") -> String { - print("\(message) [Y/n]: ", terminator: "") - - let response = readLine()?.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) - if let response, !response.isEmpty { - return response - } - return defaultValue - } -} - -// MARK: - Mise setup - -private extension CommandRunner { - /// Checks if Mise is installed, verifies if activated correctly, and handles installation + activation if necessary. - func verifyMiseIsInstalled(command: Command) async throws { - // If Mise is installed - if await shell.run("which -s mise", printOutput: false) == 0 { - print("🔵 Mise is installed.") - } else { - // Check whether it is installed in the expected path - if FileManager.default.fileExists(atPath: "\(homeDirectory)/.local/bin/mise") { - print("🟡 Mise is installed, but not activated.") - let isZshrcUpdated = await verifyAndUpdateZshrc(command: command) - if !isZshrcUpdated { - throw SetupError.miseNotActivated(command: command.rawValue) - } else { - // Force the terminal restart - exit(0) - } - } else { - let install = promptWithDefault(message: "🔴 Mise is not installed and it's required. Would you like to install it now?") - if install == "y" { - try await installMise(command: command) - } else { - throw SetupError.miseNotInstalled(command: command.rawValue) - } - } - } - } - - /// Installs Mise using a shell script and updates .zshrc to activate it. - func installMise(command: Command) async throws { - print("🔵 Installing Mise...") - await shell.runAndExitIfFailure("curl https://mise.jdx.dev/install.sh | sh", printOutput: false) - _ = await verifyAndUpdateZshrc(command: command) - exit(0) - } - - /// Verifies if the Mise activation command is present in .zshrc and prompts to add it if missing. - /// Returns: True/false whether the zshrc was successfully updated - func verifyAndUpdateZshrc(command: Command) async -> Bool { - let zshrcPath = "\(homeDirectory)/.zshrc" - let activationCommand = "eval \"$(\(homeDirectory)/.local/bin/mise activate zsh)\"" - - do { - let zshrcContents = try String(contentsOfFile: zshrcPath, encoding: .utf8) - - if !zshrcContents.contains(activationCommand) { - let response = promptWithDefault(message: "🔴 Mise activation command is not in `~/.zshrc`. Would you like to add it now?") - if response == "y" { - appendToZshrc(command: activationCommand) - print("🟡 In order to finish the Mise install, please restart your Terminal and then run `./setup.swift \(command.rawValue)` again.") - return true - } else { - printManualZshrcUpdateInstructions(command: command) - return false - } - } else { - if !isPathUpdated() { - print("🟡 Mise is activated in ~/.zshrc, but the current Terminal session does not recognize it. Please restart your Terminal and then run `./setup.swift \(command.rawValue)` again.") - exit(0) - } - } - } catch { - print("Failed to read ~/.zshrc: \(error)") - } - return true - } - - func printManualZshrcUpdateInstructions(command: Command) { - print("Please manually add the following line to your .zshrc to activate Mise:") - print("eval \"$(\(homeDirectory)/.local/bin/mise activate zsh)\"") - print("After updating, please restart your Terminal and run the run `./setup.swift \(command.rawValue)` again.") - } - - /// Appends a given command to the user's `.zshrc` file. - func appendToZshrc(command: String) { - let zshrcPath = "\(homeDirectory)/.zshrc" - let fullCommand = "\n\n# Mise \n\(command)\n" - - do { - // Check if .zshrc exists and append the command - if FileManager.default.fileExists(atPath: zshrcPath) { - let fileHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: zshrcPath)) - fileHandle.seekToEndOfFile() - if let data = fullCommand.data(using: .utf8) { - fileHandle.write(data) - fileHandle.closeFile() - print("🟢 ~/.zshrc was adjusted successfully.") - } - } else { - // If .zshrc does not exist, create it and write the command - try fullCommand.write(to: URL(fileURLWithPath: zshrcPath), atomically: true, encoding: .utf8) - print(".zshrc file created and command added.") - } - } catch { - print("An error occurred: \(error)") - } - } - - func isPathUpdated() -> Bool { - let misePath = "\(homeDirectory)/.local/bin" - let pathEnvironmentVariable = ProcessInfo.processInfo.environment["PATH"] ?? "" - return pathEnvironmentVariable.split(separator: ":").contains { String($0) == misePath } - } -} - -@MainActor -final class Shell { - private var currentTask: Process? - - // credit: https://stackoverflow.com/a/38088651 - // Shell with continuous printing to the console - // - Returns: Shell exit code - @discardableResult - func run(_ command: String, printOutput: Bool = true) async -> Int32 { - await withCheckedContinuation { continuation in - let task = Process() - currentTask = task - - let outpipe = Pipe() - task.standardOutput = outpipe - task.arguments = ["-c", command] - task.executableURL = URL(fileURLWithPath: "/bin/zsh") - task.standardInput = nil - - task.terminationHandler = { result in - DispatchQueue.main.async { - if self.currentTask === task { - self.currentTask = nil - } - continuation.resume(returning: result.terminationStatus) - } - } - - let outputHandle = outpipe.fileHandleForReading - outputHandle.readabilityHandler = { pipe in - guard printOutput else { return } - if let line = String(data: pipe.availableData, encoding: .utf8) { - print(line, terminator: "") - } else { - print("⚠️ Error decoding data: \(pipe.availableData)") - } - } - - try? task.run() - } - } - - func terminateCurrentTask() { - guard let task = currentTask else { - return - } - task.terminate() - } - - func runAndExitIfFailure(_ command: String, printOutput: Bool = true) async { - let exitCode = await run(command, printOutput: printOutput) - if exitCode != 0 { - print("❌ Command failed with exit code \(exitCode)") - exit(1) - } - } - - /// Shell with printing to the console when command terminates. - /// Allowing prompt asking for the user password. - func runWithPrivileges(_ command: String) async { - let myAppleScript = "do shell script \"\(command)\"" - var error: NSDictionary? - let scriptObject = NSAppleScript(source: myAppleScript)! - let output = scriptObject.executeAndReturnError(&error) - let ouputWithReplacedNewLines = (output.stringValue ?? "").replacingOccurrences(of: "\r\n", with: "\n") - print(ouputWithReplacedNewLines) - } -} - -await Main().run() - -// swiftlint:enable all From 037890fd4ac519ab8810efeadd4ef4e57546758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Fri, 6 Mar 2026 13:14:25 +0100 Subject: [PATCH 10/10] feat: update --- DependencyInjection.xcodeproj/project.pbxproj | 828 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../InfoPlists/DependencyInjection-Info.plist | 24 + 3 files changed, 859 insertions(+) create mode 100644 DependencyInjection.xcodeproj/project.pbxproj create mode 100644 DependencyInjection.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Derived/InfoPlists/DependencyInjection-Info.plist diff --git a/DependencyInjection.xcodeproj/project.pbxproj b/DependencyInjection.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5589c8b --- /dev/null +++ b/DependencyInjection.xcodeproj/project.pbxproj @@ -0,0 +1,828 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0F0EC10C69E47D85C55841E5 /* DependencyAutoregistering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D18ABC36825D5E4659F187 /* DependencyAutoregistering.swift */; }; + 488514BB19BCD1235EC5BEDF /* Injected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D716BA60E15D9B99BDA81D6 /* Injected.swift */; }; + 4AADD60495AB1BDF90A77CA7 /* AsyncDependencyRegistering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0540BAF9226627466BCA29AB /* AsyncDependencyRegistering.swift */; }; + 59646DF77FBE0893B71FEBB6 /* AsyncContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006AB02F9AFCF0678724ACB9 /* AsyncContainer.swift */; }; + 5C4783670D99CC0316AEED4A /* DependencyResolving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62440DB606CAA4E38D984CB3 /* DependencyResolving.swift */; }; + 655533DA195952C302B5AB2E /* RegistrationIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BDE1CF0A78F03F4A27F775 /* RegistrationIdentifier.swift */; }; + 677FEF0962856B9EEF8C924C /* Registration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527CBA78FAF3168AA5D980F1 /* Registration.swift */; }; + 7072AF811C6B5FBDF4E75E64 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322A71514D6BD32F1C3A1171 /* Container.swift */; }; + 7550578D5840A448EEA17F46 /* ResolutionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 744089AF193032D0720430FB /* ResolutionError.swift */; }; + 7B15A1A1BC0CB331CA53DF76 /* ModuleRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A218F3B8405E479560AA7EAA /* ModuleRegistration.swift */; }; + 996E7D36CF4F9FA5CE407CEB /* DependencyRegistering.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F26A8A6CCA0F65EB53F4B8 /* DependencyRegistering.swift */; }; + A9F38041C5E8D4B4A0E06E16 /* AsyncDependencyResolving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27072096872F0240389424C8 /* AsyncDependencyResolving.swift */; }; + ACC3DF3ED2C63A2693EC8F63 /* DependencyScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A8A7504841D604B2E85C78 /* DependencyScope.swift */; }; + D4868CF52C04FDF9B0839784 /* AsyncModuleRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6EFDA31ADF86E697192161 /* AsyncModuleRegistration.swift */; }; + DD0B875517645D5AED1CC698 /* AsyncRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EFE9E8F1052ED64B98F7D /* AsyncRegistration.swift */; }; + F5A50CCD66C7D0BEFAD06854 /* LazyInjected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7178595BF3600203049C2562 /* LazyInjected.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + B8F515DE0266CCC8C80B3C89 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 006AB02F9AFCF0678724ACB9 /* AsyncContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncContainer.swift; sourceTree = ""; }; + 0540BAF9226627466BCA29AB /* AsyncDependencyRegistering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncDependencyRegistering.swift; sourceTree = ""; }; + 27072096872F0240389424C8 /* AsyncDependencyResolving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncDependencyResolving.swift; sourceTree = ""; }; + 322A71514D6BD32F1C3A1171 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 3D716BA60E15D9B99BDA81D6 /* Injected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injected.swift; sourceTree = ""; }; + 527CBA78FAF3168AA5D980F1 /* Registration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Registration.swift; sourceTree = ""; }; + 62440DB606CAA4E38D984CB3 /* DependencyResolving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyResolving.swift; sourceTree = ""; }; + 64BDE1CF0A78F03F4A27F775 /* RegistrationIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationIdentifier.swift; sourceTree = ""; }; + 7178595BF3600203049C2562 /* LazyInjected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyInjected.swift; sourceTree = ""; }; + 744089AF193032D0720430FB /* ResolutionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolutionError.swift; sourceTree = ""; }; + 7EBFBFA6FBFF226C37CA713F /* DependencyInjection-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "DependencyInjection-Info.plist"; sourceTree = ""; }; + 841EFE9E8F1052ED64B98F7D /* AsyncRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncRegistration.swift; sourceTree = ""; }; + 86D18ABC36825D5E4659F187 /* DependencyAutoregistering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyAutoregistering.swift; sourceTree = ""; }; + A218F3B8405E479560AA7EAA /* ModuleRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleRegistration.swift; sourceTree = ""; }; + A6A8A7504841D604B2E85C78 /* DependencyScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyScope.swift; sourceTree = ""; }; + B7F26A8A6CCA0F65EB53F4B8 /* DependencyRegistering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyRegistering.swift; sourceTree = ""; }; + CC6EFDA31ADF86E697192161 /* AsyncModuleRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncModuleRegistration.swift; sourceTree = ""; }; + D8973C5E0C32FA7A06D80897 /* DependencyInjection.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DependencyInjection.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3DD8845A43EC7AAD06E546F6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 06113DA7F7696A1E15A9B597 /* Sync */ = { + isa = PBXGroup; + children = ( + 62440DB606CAA4E38D984CB3 /* DependencyResolving.swift */, + A218F3B8405E479560AA7EAA /* ModuleRegistration.swift */, + ); + path = Sync; + sourceTree = ""; + }; + 14A9C76ABD40223F48DAB7CB /* Registration */ = { + isa = PBXGroup; + children = ( + 3D0A77BAB3864DCE7FEAF380 /* Async */, + 1B4AD32692995E22CCAF24C2 /* Sync */, + ); + path = Registration; + sourceTree = ""; + }; + 1B4AD32692995E22CCAF24C2 /* Sync */ = { + isa = PBXGroup; + children = ( + 86D18ABC36825D5E4659F187 /* DependencyAutoregistering.swift */, + B7F26A8A6CCA0F65EB53F4B8 /* DependencyRegistering.swift */, + ); + path = Sync; + sourceTree = ""; + }; + 30AD42D71BABFB1FE473A4AC /* Protocols */ = { + isa = PBXGroup; + children = ( + 14A9C76ABD40223F48DAB7CB /* Registration */, + DE80C6B58BEB33BC885DEF9C /* Resolution */, + ); + path = Protocols; + sourceTree = ""; + }; + 375C34F992371F392135D0A3 /* Sync */ = { + isa = PBXGroup; + children = ( + 322A71514D6BD32F1C3A1171 /* Container.swift */, + ); + path = Sync; + sourceTree = ""; + }; + 3D0A77BAB3864DCE7FEAF380 /* Async */ = { + isa = PBXGroup; + children = ( + 0540BAF9226627466BCA29AB /* AsyncDependencyRegistering.swift */, + ); + path = Async; + sourceTree = ""; + }; + 41F34376FC297AD4F75303C5 /* Async */ = { + isa = PBXGroup; + children = ( + 27072096872F0240389424C8 /* AsyncDependencyResolving.swift */, + CC6EFDA31ADF86E697192161 /* AsyncModuleRegistration.swift */, + ); + path = Async; + sourceTree = ""; + }; + 610CC198FAB5EEEAF9B9CAA5 /* PropertyWrappers */ = { + isa = PBXGroup; + children = ( + 3D716BA60E15D9B99BDA81D6 /* Injected.swift */, + 7178595BF3600203049C2562 /* LazyInjected.swift */, + ); + path = PropertyWrappers; + sourceTree = ""; + }; + 7328925C0447DEF24AFCE757 = { + isa = PBXGroup; + children = ( + C6C8C5285D241145D3E3ECA7 /* Project */, + 9C53AFD18ED7AB4CF4F7AAA6 /* Products */, + ); + sourceTree = ""; + }; + 8BD81DA2F70967A73FC671D2 /* InfoPlists */ = { + isa = PBXGroup; + children = ( + 7EBFBFA6FBFF226C37CA713F /* DependencyInjection-Info.plist */, + ); + path = InfoPlists; + sourceTree = ""; + }; + 8ED94F0A0C528D10245A9AE1 /* Models */ = { + isa = PBXGroup; + children = ( + E4070327992386902B1054C7 /* Async */, + D278BC0783FBA32668E12BA0 /* Sync */, + A6A8A7504841D604B2E85C78 /* DependencyScope.swift */, + 64BDE1CF0A78F03F4A27F775 /* RegistrationIdentifier.swift */, + 744089AF193032D0720430FB /* ResolutionError.swift */, + ); + path = Models; + sourceTree = ""; + }; + 9C53AFD18ED7AB4CF4F7AAA6 /* Products */ = { + isa = PBXGroup; + children = ( + D8973C5E0C32FA7A06D80897 /* DependencyInjection.framework */, + ); + name = Products; + sourceTree = ""; + }; + C3DA8F2DBDF0D391C6EF142A /* Container */ = { + isa = PBXGroup; + children = ( + D0D51FC40B568488124C16BB /* Async */, + 375C34F992371F392135D0A3 /* Sync */, + ); + path = Container; + sourceTree = ""; + }; + C6C8C5285D241145D3E3ECA7 /* Project */ = { + isa = PBXGroup; + children = ( + E6C797B85EBA09B9CB5E9AD3 /* Derived */, + ED496E4164DCDDDE765249AA /* Sources */, + ); + name = Project; + sourceTree = ""; + }; + D0D51FC40B568488124C16BB /* Async */ = { + isa = PBXGroup; + children = ( + 006AB02F9AFCF0678724ACB9 /* AsyncContainer.swift */, + ); + path = Async; + sourceTree = ""; + }; + D278BC0783FBA32668E12BA0 /* Sync */ = { + isa = PBXGroup; + children = ( + 527CBA78FAF3168AA5D980F1 /* Registration.swift */, + ); + path = Sync; + sourceTree = ""; + }; + DE80C6B58BEB33BC885DEF9C /* Resolution */ = { + isa = PBXGroup; + children = ( + 41F34376FC297AD4F75303C5 /* Async */, + 06113DA7F7696A1E15A9B597 /* Sync */, + ); + path = Resolution; + sourceTree = ""; + }; + E4070327992386902B1054C7 /* Async */ = { + isa = PBXGroup; + children = ( + 841EFE9E8F1052ED64B98F7D /* AsyncRegistration.swift */, + ); + path = Async; + sourceTree = ""; + }; + E6C797B85EBA09B9CB5E9AD3 /* Derived */ = { + isa = PBXGroup; + children = ( + 8BD81DA2F70967A73FC671D2 /* InfoPlists */, + ); + path = Derived; + sourceTree = ""; + }; + ED496E4164DCDDDE765249AA /* Sources */ = { + isa = PBXGroup; + children = ( + C3DA8F2DBDF0D391C6EF142A /* Container */, + 8ED94F0A0C528D10245A9AE1 /* Models */, + 610CC198FAB5EEEAF9B9CAA5 /* PropertyWrappers */, + 30AD42D71BABFB1FE473A4AC /* Protocols */, + ); + path = Sources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 066B6D94E318585A3823B92C /* DependencyInjection */ = { + isa = PBXNativeTarget; + buildConfigurationList = E0142061E08F2E181A8D4D2A /* Build configuration list for PBXNativeTarget "DependencyInjection" */; + buildPhases = ( + 99314D14DB38963C89289A31 /* Sources */, + DA315482D92E0489CEBF5DCE /* Resources */, + B8F515DE0266CCC8C80B3C89 /* Embed Frameworks */, + 3DD8845A43EC7AAD06E546F6 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DependencyInjection; + packageProductDependencies = ( + ); + productName = DependencyInjection; + productReference = D8973C5E0C32FA7A06D80897 /* DependencyInjection.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 14C0EC1B68C144FE601BAFF5 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 9999; + TargetAttributes = { + }; + }; + buildConfigurationList = 2A8A546792FE3A4C3F2EBAAC /* Build configuration list for PBXProject "DependencyInjection" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 7328925C0447DEF24AFCE757; + productRefGroup = 9C53AFD18ED7AB4CF4F7AAA6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 066B6D94E318585A3823B92C /* DependencyInjection */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + DA315482D92E0489CEBF5DCE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 99314D14DB38963C89289A31 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 59646DF77FBE0893B71FEBB6 /* AsyncContainer.swift in Sources */, + 7072AF811C6B5FBDF4E75E64 /* Container.swift in Sources */, + DD0B875517645D5AED1CC698 /* AsyncRegistration.swift in Sources */, + ACC3DF3ED2C63A2693EC8F63 /* DependencyScope.swift in Sources */, + 655533DA195952C302B5AB2E /* RegistrationIdentifier.swift in Sources */, + 7550578D5840A448EEA17F46 /* ResolutionError.swift in Sources */, + 677FEF0962856B9EEF8C924C /* Registration.swift in Sources */, + 488514BB19BCD1235EC5BEDF /* Injected.swift in Sources */, + F5A50CCD66C7D0BEFAD06854 /* LazyInjected.swift in Sources */, + 4AADD60495AB1BDF90A77CA7 /* AsyncDependencyRegistering.swift in Sources */, + 0F0EC10C69E47D85C55841E5 /* DependencyAutoregistering.swift in Sources */, + 996E7D36CF4F9FA5CE407CEB /* DependencyRegistering.swift in Sources */, + A9F38041C5E8D4B4A0E06E16 /* AsyncDependencyResolving.swift in Sources */, + D4868CF52C04FDF9B0839784 /* AsyncModuleRegistration.swift in Sources */, + 5C4783670D99CC0316AEED4A /* DependencyResolving.swift in Sources */, + 7B15A1A1BC0CB331CA53DF76 /* ModuleRegistration.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 0B4CDCE67DBEE3BDBD9D9B00 /* Production-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + VALIDATE_PRODUCT = YES; + }; + name = "Production-Release"; + }; + 3E150CB2E5A9FCFD1C934365 /* Development-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + VALIDATE_PRODUCT = YES; + }; + name = "Development-Release"; + }; + 4E66FC02FE60654E3FBE9945 /* Development-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + GCC_NO_COMMON_BLOCKS = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + INFOPLIST_FILE = "Derived/InfoPlists/DependencyInjection-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)", + "-package-name", + DependencyInjection, + "$(inherited)", + "-enable-upcoming-feature \"StrictConcurrency\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = DependencyInjection; + PRODUCT_NAME = DependencyInjection; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE APPLICATION_EXTENSION_API_ONLY"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SUPPRESS_WARNINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USE_HEADERMAP = NO; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Development-Release"; + }; + 75FE922437F06C18503B957F /* Production-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Production-Debug"; + }; + 950B087A5036CEA20657EC16 /* Development-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Development-Debug"; + }; + A384D36B5732E0952C75C348 /* Development-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + GCC_NO_COMMON_BLOCKS = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + INFOPLIST_FILE = "Derived/InfoPlists/DependencyInjection-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)", + "-package-name", + DependencyInjection, + "$(inherited)", + "-enable-upcoming-feature \"StrictConcurrency\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = DependencyInjection; + PRODUCT_NAME = DependencyInjection; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited) SWIFT_PACKAGE APPLICATION_EXTENSION_API_ONLY", + DEBUG, + ); + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SUPPRESS_WARNINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USE_HEADERMAP = NO; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Development-Debug"; + }; + B6663F9B8A758FC62002180D /* Production-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + GCC_NO_COMMON_BLOCKS = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + INFOPLIST_FILE = "Derived/InfoPlists/DependencyInjection-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)", + "-package-name", + DependencyInjection, + "$(inherited)", + "-enable-upcoming-feature \"StrictConcurrency\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = DependencyInjection; + PRODUCT_NAME = DependencyInjection; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited) SWIFT_PACKAGE APPLICATION_EXTENSION_API_ONLY", + DEBUG, + ); + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SUPPRESS_WARNINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USE_HEADERMAP = NO; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Production-Debug"; + }; + FC0473BE3DA6642EE0888FE2 /* Production-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + GCC_NO_COMMON_BLOCKS = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + INFOPLIST_FILE = "Derived/InfoPlists/DependencyInjection-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)", + "-package-name", + DependencyInjection, + "$(inherited)", + "-enable-upcoming-feature \"StrictConcurrency\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = DependencyInjection; + PRODUCT_NAME = DependencyInjection; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE APPLICATION_EXTENSION_API_ONLY"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SUPPRESS_WARNINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + USE_HEADERMAP = NO; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Production-Release"; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A8A546792FE3A4C3F2EBAAC /* Build configuration list for PBXProject "DependencyInjection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 950B087A5036CEA20657EC16 /* Development-Debug */, + 3E150CB2E5A9FCFD1C934365 /* Development-Release */, + 75FE922437F06C18503B957F /* Production-Debug */, + 0B4CDCE67DBEE3BDBD9D9B00 /* Production-Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Development-Release"; + }; + E0142061E08F2E181A8D4D2A /* Build configuration list for PBXNativeTarget "DependencyInjection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A384D36B5732E0952C75C348 /* Development-Debug */, + 4E66FC02FE60654E3FBE9945 /* Development-Release */, + B6663F9B8A758FC62002180D /* Production-Debug */, + FC0473BE3DA6642EE0888FE2 /* Production-Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Development-Release"; + }; +/* End XCConfigurationList section */ + }; + rootObject = 14C0EC1B68C144FE601BAFF5 /* Project object */; +} diff --git a/DependencyInjection.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/DependencyInjection.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/DependencyInjection.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Derived/InfoPlists/DependencyInjection-Info.plist b/Derived/InfoPlists/DependencyInjection-Info.plist new file mode 100644 index 0000000..ae6e1f4 --- /dev/null +++ b/Derived/InfoPlists/DependencyInjection-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright ©. All rights reserved. + +