diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56499e0..6bf8f20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,10 @@ env: jobs: macOS: name: macOS - runs-on: macOS-latest + runs-on: macOS-15 + env: + DEVELOPER_DIR: /Applications/Xcode_16.0.app steps: - - uses: actions/checkout@v2 - - name: Swift - uses: swift-actions/setup-swift@v1 - - name: Preparation - run: set -o pipefail + - uses: actions/checkout@v3 - name: Build run: make build diff --git a/.gitignore b/.gitignore index 056e851..5192a32 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ fastlane/test_output # AnalyticsGen analyticsgen-*.zip +**/Generated/AnalyticsGen diff --git a/Example/.analyticsGen.lock b/Example/.analyticsGen.lock index 4b2f781..08eddfb 100644 --- a/Example/.analyticsGen.lock +++ b/Example/.analyticsGen.lock @@ -1,9 +1,9 @@ HR-Mobile: - sha: 40a1900a6733c3e64d1b82e1492df7b3daef711c - version: 0.6.0 + sha: 1dcac2e8387cdfe140269fffe398b920b6bfb671 + version: 0.6.6 Applicant: - sha: 40a1900a6733c3e64d1b82e1492df7b3daef711c - version: 0.6.0 + sha: 1dcac2e8387cdfe140269fffe398b920b6bfb671 + version: 0.6.6 Shared: - sha: 40a1900a6733c3e64d1b82e1492df7b3daef711c - version: 0.6.0 + sha: 1dcac2e8387cdfe140269fffe398b920b6bfb671 + version: 0.6.6 diff --git a/Sources/AnalyticsGen/Generators/Event/DefaultEventGenerator.swift b/Sources/AnalyticsGen/Generators/Event/DefaultEventGenerator.swift index 794fa35..a12a1e1 100644 --- a/Sources/AnalyticsGen/Generators/Event/DefaultEventGenerator.swift +++ b/Sources/AnalyticsGen/Generators/Event/DefaultEventGenerator.swift @@ -250,7 +250,7 @@ final class DefaultEventGenerator: EventGenerator { return lockReference != remoteLockReference } - private func saveLockfile(configurationName: String, remoteReferenceSHA: String) throws { + private func saveLockfile(configurationNames: [String], remoteReferenceSHA: String) throws { var lockGitRerences = ( try? fileProvider.readFileIfExists( at: .lockFilePath, @@ -258,43 +258,79 @@ final class DefaultEventGenerator: EventGenerator { ) ) ?? [:] - lockGitRerences[configurationName] = LockReference( - sha: remoteReferenceSHA, - version: currentVersion - ) + configurationNames.forEach { configurationName in + lockGitRerences[configurationName] = LockReference( + sha: remoteReferenceSHA, + version: currentVersion + ) + } try fileProvider.writeFile(content: lockGitRerences, at: .lockFilePath) } + + private func saveLockfile(configurationName: String, remoteReferenceSHA: String) throws { + try saveLockfile(configurationNames: [configurationName], remoteReferenceSHA: remoteReferenceSHA) + } - private func generateFromRemoteRepo( + private func generateFromRemoteRepoCore( remoteRepoConfiguration: RemoteRepoSourceConfiguration, ref: GitReferenceType, - configuration: GeneratedConfiguration, - remoteReferenceSHA: String + configurations: [GeneratedConfiguration], + remoteReferenceSHA: String, + useConfigurationKey: Bool = false ) -> Promise { - firstly { + let key = useConfigurationKey ? configurations.first?.name ?? "Main" : "Main" + + return firstly { remoteRepoProvider.fetchRepo( owner: remoteRepoConfiguration.owner, repo: remoteRepoConfiguration.repo, ref: ref, token: try remoteRepoConfiguration.accessToken.resolveToken(), - key: configuration.name - ) - }.map { repoPathURL in - try self.generate( - configuration: configuration, - targetPath: remoteRepoConfiguration.path, - schemasPath: remoteRepoConfiguration.path.map { targetPath in - repoPathURL.appendingPathComponent(targetPath) - } ?? repoPathURL + key: key ) + }.then { repoPathURL in + let generationPromises = configurations.map { configuration in + perform(on: .global()) { + let targetPath = useConfigurationKey ? remoteRepoConfiguration.path : configuration.source.remoteRepoConfiguration?.path + let schemasPath = targetPath.map { targetPath in + repoPathURL.appendingPathComponent(targetPath) + } ?? repoPathURL + + try self.generate( + configuration: configuration, + targetPath: targetPath, + schemasPath: schemasPath + ) + } + } - try self.saveLockfile(configurationName: configuration.name, remoteReferenceSHA: remoteReferenceSHA) + return when(fulfilled: generationPromises) + }.map { + try self.saveLockfile( + configurationNames: configurations.map { $0.name }, + remoteReferenceSHA: remoteReferenceSHA + ) return .success } } + private func generateFromRemoteRepo( + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + ref: GitReferenceType, + configuration: GeneratedConfiguration, + remoteReferenceSHA: String + ) -> Promise { + return generateFromRemoteRepoCore( + remoteRepoConfiguration: remoteRepoConfiguration, + ref: ref, + configurations: [configuration], + remoteReferenceSHA: remoteReferenceSHA, + useConfigurationKey: true + ) + } + private func fetchRemoteReferenceSHA( remoteRepoConfiguration: RemoteRepoSourceConfiguration, gitReferenceType: GitReferenceType @@ -317,26 +353,33 @@ final class DefaultEventGenerator: EventGenerator { } } - private func generateFromRemoteRepo( + private func generateFromRemoteRepoWithForce( remoteRepoConfiguration: RemoteRepoSourceConfiguration, gitReferenceType: GitReferenceType, - configuration: GeneratedConfiguration, - force: Bool + configurations: [GeneratedConfiguration], + force: Bool, + useConfigurationKey: Bool = false ) -> Promise { firstly { - fetchRemoteReferenceSHA(remoteRepoConfiguration: remoteRepoConfiguration, gitReferenceType: gitReferenceType) - }.then { remoteReferenceSHA in - let shouldPerformGeneration = try self.shouldGenerate( - configuration: configuration, - remoteReferenceSHA: remoteReferenceSHA + fetchRemoteReferenceSHA( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: gitReferenceType ) + }.then { remoteReferenceSHA in + let shouldPerformGeneration = try configurations.contains { configuration in + try self.shouldGenerate( + configuration: configuration, + remoteReferenceSHA: remoteReferenceSHA + ) + } if shouldPerformGeneration || force { - return self.generateFromRemoteRepo( + return self.generateFromRemoteRepoCore( remoteRepoConfiguration: remoteRepoConfiguration, ref: gitReferenceType, - configuration: configuration, - remoteReferenceSHA: remoteReferenceSHA + configurations: configurations, + remoteReferenceSHA: remoteReferenceSHA, + useConfigurationKey: useConfigurationKey ) } else { return .value(.upToDate) @@ -344,19 +387,41 @@ final class DefaultEventGenerator: EventGenerator { } } - private func performFinders( - finderConfigurations: [RemoteRepoReferenceFinderConfiguration], + private func generateFromRemoteRepo( remoteRepoConfiguration: RemoteRepoSourceConfiguration, - generatedConfiguration: GeneratedConfiguration, + gitReferenceType: GitReferenceType, + configuration: GeneratedConfiguration, force: Bool - ) throws -> Promise { - Log.info( - String( - format: "(%@) Searching remote repo reference via %d finder(s)...", - generatedConfiguration.name, - finderConfigurations.count - ) + ) -> Promise { + return generateFromRemoteRepoWithForce( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: gitReferenceType, + configurations: [configuration], + force: force, + useConfigurationKey: true ) + } + + private func performFindersCore( + finderConfigurations: [RemoteRepoReferenceFinderConfiguration], + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + generatedConfigurations: [GeneratedConfiguration], + force: Bool, + useConfigurationName: Bool = false + ) throws -> Promise { + let configurationName = useConfigurationName ? generatedConfigurations.first?.name : nil + let logFormat = useConfigurationName + ? "(%@) Searching remote repo reference via %d finder(s)..." + : "Searching remote repo reference via %d finder(s)..." + let findLogFormat = useConfigurationName + ? "(%@) Found remote repo reference '%@'." + : "Found remote repo reference '%@'." + + if let configurationName = configurationName { + Log.info(String(format: logFormat, configurationName, finderConfigurations.count)) + } else { + Log.info(String(format: logFormat, finderConfigurations.count)) + } return try remoteRepoReferenceFinder .findReference( @@ -371,66 +436,202 @@ final class DefaultEventGenerator: EventGenerator { } } .get { (gitReferenceType: GitReferenceType) in - Log.info( - String( - format: "(%@) Found remote repo reference '%@'.", - generatedConfiguration.name, - gitReferenceType.rawValue - ) - ) + if let configurationName = configurationName { + Log.info(String(format: findLogFormat, configurationName, gitReferenceType.rawValue)) + } else { + Log.info(String(format: findLogFormat, gitReferenceType.rawValue)) + } } .then { gitReferenceType in - self.generateFromRemoteRepo( - remoteRepoConfiguration: remoteRepoConfiguration, - gitReferenceType: gitReferenceType, - configuration: generatedConfiguration, - force: force - ) + if generatedConfigurations.count == 1 && useConfigurationName { + return self.generateFromRemoteRepo( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: gitReferenceType, + configuration: generatedConfigurations[0], + force: force + ) + } else { + return self.generateFromRemoteRepo( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: gitReferenceType, + configurations: generatedConfigurations, + force: force + ) + } } } - private func generate( - configuration: GeneratedConfiguration, + private func performFinders( + finderConfigurations: [RemoteRepoReferenceFinderConfiguration], + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + generatedConfiguration: GeneratedConfiguration, force: Bool ) throws -> Promise { - switch configuration.source { - case .local(let path): - try generate( - configuration: configuration, - schemasPath: URL(fileURLWithPath: path) - ) - - return .value(.success) + return try performFindersCore( + finderConfigurations: finderConfigurations, + remoteRepoConfiguration: remoteRepoConfiguration, + generatedConfigurations: [generatedConfiguration], + force: force, + useConfigurationName: true + ) + } - case .remoteRepo(let remoteRepoConfiguration): - switch remoteRepoConfiguration.ref { - case .tag(let name): + private func handleRemoteRepoReference( + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + configurations: [GeneratedConfiguration], + force: Bool, + useSingleConfiguration: Bool = false + ) throws -> Promise { + switch remoteRepoConfiguration.ref { + case .tag(let name): + if useSingleConfiguration && configurations.count == 1 { return generateFromRemoteRepo( remoteRepoConfiguration: remoteRepoConfiguration, gitReferenceType: .tag(name: name), - configuration: configuration, + configuration: configurations[0], force: force ) + } else { + return generateFromRemoteRepo( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: .tag(name: name), + configurations: configurations, + force: force + ) + } - case .branch(let name): + case .branch(let name): + if useSingleConfiguration && configurations.count == 1 { return generateFromRemoteRepo( remoteRepoConfiguration: remoteRepoConfiguration, gitReferenceType: .branch(name: name), - configuration: configuration, + configuration: configurations[0], force: force ) + } else { + return generateFromRemoteRepo( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: .branch(name: name), + configurations: configurations, + force: force + ) + } - case .finders(let finders): + case .finders(let finders): + if useSingleConfiguration && configurations.count == 1 { + return try performFinders( + finderConfigurations: finders, + remoteRepoConfiguration: remoteRepoConfiguration, + generatedConfiguration: configurations[0], + force: force + ) + } else { return try performFinders( finderConfigurations: finders, remoteRepoConfiguration: remoteRepoConfiguration, - generatedConfiguration: configuration, + generatedConfigurations: configurations, force: force ) } } } + private func generate( + configuration: GeneratedConfiguration, + force: Bool + ) throws -> Promise { + switch configuration.source { + case .local(let path): + try generate( + configuration: configuration, + schemasPath: URL(fileURLWithPath: path) + ) + + return .value(.success) + + case .remoteRepo(let remoteRepoConfiguration): + return try handleRemoteRepoReference( + remoteRepoConfiguration: remoteRepoConfiguration, + configurations: [configuration], + force: force, + useSingleConfiguration: true + ) + } + } + + private func generateFromRemoteRepo( + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + ref: GitReferenceType, + configurations: [GeneratedConfiguration], + remoteReferenceSHA: String + ) -> Promise { + return generateFromRemoteRepoCore( + remoteRepoConfiguration: remoteRepoConfiguration, + ref: ref, + configurations: configurations, + remoteReferenceSHA: remoteReferenceSHA, + useConfigurationKey: false + ) + } + + private func generateFromRemoteRepo( + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + gitReferenceType: GitReferenceType, + configurations: [GeneratedConfiguration], + force: Bool + ) -> Promise { + return generateFromRemoteRepoWithForce( + remoteRepoConfiguration: remoteRepoConfiguration, + gitReferenceType: gitReferenceType, + configurations: configurations, + force: force, + useConfigurationKey: false + ) + } + + private func performFinders( + finderConfigurations: [RemoteRepoReferenceFinderConfiguration], + remoteRepoConfiguration: RemoteRepoSourceConfiguration, + generatedConfigurations: [GeneratedConfiguration], + force: Bool + ) throws -> Promise { + return try performFindersCore( + finderConfigurations: finderConfigurations, + remoteRepoConfiguration: remoteRepoConfiguration, + generatedConfigurations: generatedConfigurations, + force: force, + useConfigurationName: false + ) + } + + private func generate( + from source: SourceConfiguration, + for configurations: [GeneratedConfiguration], + force: Bool + ) throws -> Promise { + switch source { + case .local(let path): + let generationPromises = configurations.map { configuration in + perform(on: .global()) { + try self.generate( + configuration: configuration, + schemasPath: URL(fileURLWithPath: path) + ) + } + } + + return when(fulfilled: generationPromises).map { .success } + + case .remoteRepo(let remoteRepoConfiguration): + return try handleRemoteRepoReference( + remoteRepoConfiguration: remoteRepoConfiguration, + configurations: configurations, + force: force, + useSingleConfiguration: false + ) + } + } + // MARK: - EventGenerator func generate(configuration: Configuration, force: Bool) -> Promise { @@ -441,7 +642,16 @@ final class DefaultEventGenerator: EventGenerator { }.get { configurations in Log.info("Found \(configurations.count) configurations\n") }.then(on: .global()) { configurations in - when(fulfilled: try configurations.map { try self.generate(configuration: $0, force: force) }) + if configurations.allSatisfy({ $0.source.remoteRepoConfiguration?.ref == configuration.source.remoteRepoConfiguration?.ref }) { + try self.generate( + from: configuration.source, + for: configurations, + force: force + ) + .map { [$0] } + } else { + when(fulfilled: try configurations.map { try self.generate(configuration: $0, force: force) }) + } }.map { results in results.contains(.success) ? .success : .upToDate } diff --git a/Sources/AnalyticsGen/Models/Configuration/SourceConfiguration.swift b/Sources/AnalyticsGen/Models/Configuration/SourceConfiguration.swift index 39797a4..30715b7 100644 --- a/Sources/AnalyticsGen/Models/Configuration/SourceConfiguration.swift +++ b/Sources/AnalyticsGen/Models/Configuration/SourceConfiguration.swift @@ -2,21 +2,22 @@ import Foundation enum SourceConfiguration: Decodable, Equatable { - // MARK: - Nested Types - private enum CodingKeys: String, CodingKey { - - // MARK: - Enumeration Cases - case remoteRepo } - // MARK: - Instance Properties - case local(path: String) case remoteRepo(configuration: RemoteRepoSourceConfiguration) - // MARK: - Initializers + var remoteRepoConfiguration: RemoteRepoSourceConfiguration? { + switch self { + case .remoteRepo(configuration: let configuration): + return configuration + + default: + return nil + } + } init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { @@ -28,6 +29,7 @@ enum SourceConfiguration: Decodable, Equatable { } extension SourceConfiguration { + init(source: SourceConfiguration, target: Target) { switch source { case .local: diff --git a/Sources/AnalyticsGen/Providers/Forgejo/ForgejoRemoteRepoProvider.swift b/Sources/AnalyticsGen/Providers/Forgejo/ForgejoRemoteRepoProvider.swift index 1e6ad2b..8ce572b 100644 --- a/Sources/AnalyticsGen/Providers/Forgejo/ForgejoRemoteRepoProvider.swift +++ b/Sources/AnalyticsGen/Providers/Forgejo/ForgejoRemoteRepoProvider.swift @@ -44,7 +44,7 @@ struct ForgejoRemoteRepoProvider: RemoteRepoProvider { { Log.debug("Trying to merge master into user branch") try shell("git clone -b \(name) \(gitRepositoryURL) \(repositoryPath)") - try shell("cd \(repositoryPath) && git fetch --depth 1 origin refs/heads/master:refs/remotes/origin/master") + try shell("cd \(repositoryPath) && git fetch origin refs/heads/master:refs/remotes/origin/master") try shell("cd \(repositoryPath) && git merge origin/master --no-edit") } else { try shell("git clone --depth 1 -b \(name) \(gitRepositoryURL) \(repositoryPath)")