Sources/XCRemoteCache/Commands/SwiftFrontend/SwiftFrontendArgInput.swift (193 lines of code) (raw):

// Copyright (c) 2023 Spotify AB. // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import Foundation enum SwiftFrontendArgInputError: Error, Equatable { // swift-frontend should either be compling or emiting a module case bothCompilationAndEmitAction // no .swift files have been passed as input files case noCompilationInputs // no -primary-file .swift files have been passed as input files case noPrimaryFileCompilationInputs // number of -emit-dependencies-path doesn't match compilation inputs case dependenciesOuputCountDoesntMatch(expected: Int, parsed: Int) // number of -serialize-diagnostics-path doesn't match compilation inputs case diagnosticsOuputCountDoesntMatch(expected: Int, parsed: Int) // number of -o doesn't match compilation inputs case outputsOuputCountDoesntMatch(expected: Int, parsed: Int) // number of -o for emit-module can be only 1 case emitModulOuputCountIsNot1(parsed: Int) // number of -emit-dependencies-path for emit-module can be 0 or 1 (generate or not) case emitModuleDependenciesOuputCountIsHigherThan1(parsed: Int) // number of -serialize-diagnostics-path for emit-module can be 0 or 1 (generate or not) case emitModuleDiagnosticsOuputCountIsHigherThan1(parsed: Int) // emit-module requires -emit-objc-header-path case emitModuleMissingObjcHeaderPath // -target is required case emitMissingTarget // -moduleName is required case emitMissingModuleName } public struct SwiftFrontendArgInput { let compile: Bool let emitModule: Bool let objcHeaderOutput: String? let moduleName: String? let target: String? let primaryInputPaths: [String] let inputPaths: [String] var outputPaths: [String] var dependenciesPaths: [String] // Extra params // Diagnostics are not supported yet in the XCRemoteCache (cached artifacts assumes no warnings) var diagnosticsPaths: [String] // Unsed for now: // .swiftsourceinfo and .swiftdoc will be placed next to the .swiftmodule let sourceInfoPath: String? let docPath: String? // Passed as -supplementary-output-file-map let supplementaryOutputFileMap: String? /// Manual initializer implementation required to be public public init( compile: Bool, emitModule: Bool, objcHeaderOutput: String?, moduleName: String?, target: String?, primaryInputPaths: [String], inputPaths: [String], outputPaths: [String], dependenciesPaths: [String], diagnosticsPaths: [String], sourceInfoPath: String?, docPath: String?, supplementaryOutputFileMap: String? ) { self.compile = compile self.emitModule = emitModule self.objcHeaderOutput = objcHeaderOutput self.moduleName = moduleName self.target = target self.primaryInputPaths = primaryInputPaths self.inputPaths = inputPaths self.outputPaths = outputPaths self.dependenciesPaths = dependenciesPaths self.diagnosticsPaths = diagnosticsPaths self.sourceInfoPath = sourceInfoPath self.docPath = docPath self.supplementaryOutputFileMap = supplementaryOutputFileMap } private func generateForCompilation( config: XCRemoteCacheConfig, target: String, moduleName: String ) throws -> SwiftcContext { let primaryInputsCount = primaryInputPaths.count guard primaryInputsCount > 0 else { throw SwiftFrontendArgInputError.noPrimaryFileCompilationInputs } guard [primaryInputsCount, 0].contains(dependenciesPaths.count) else { throw SwiftFrontendArgInputError.dependenciesOuputCountDoesntMatch( expected: primaryInputsCount, parsed: dependenciesPaths.count ) } guard [primaryInputsCount, 0].contains(diagnosticsPaths.count) else { throw SwiftFrontendArgInputError.diagnosticsOuputCountDoesntMatch( expected: primaryInputsCount, parsed: diagnosticsPaths.count ) } guard outputPaths.count == primaryInputsCount else { throw SwiftFrontendArgInputError.outputsOuputCountDoesntMatch( expected: primaryInputsCount, parsed: outputPaths.count ) } let primaryInputFilesURLs: [URL] = primaryInputPaths.map(URL.init(fileURLWithPath:)) let steps: SwiftcContext.SwiftcSteps = SwiftcContext.SwiftcSteps( compileFilesScope: .subset(primaryInputFilesURLs), emitModule: nil ) let compilationFilesInputs = buildCompilationFilesInputs( primaryInputsCount: primaryInputsCount, primaryInputFilesURLs: primaryInputFilesURLs ) return try .init( config: config, moduleName: moduleName, steps: steps, inputs: compilationFilesInputs, target: target, compilationFiles: .list(inputPaths), exampleWorkspaceFilePath: outputPaths[0] ) } private func buildCompilationFilesInputs( primaryInputsCount: Int, primaryInputFilesURLs: [URL] ) -> SwiftcContext.CompilationFilesInputs { if let compimentaryFileMa = supplementaryOutputFileMap { return .supplementaryFileMap(compimentaryFileMa) } else { return .map((0..<primaryInputsCount).reduce( [String: SwiftFileCompilationInfo]() ) { prev, i in var new = prev new[primaryInputPaths[i]] = SwiftFileCompilationInfo( file: primaryInputFilesURLs[i], dependencies: dependenciesPaths.get(i).map(URL.init(fileURLWithPath:)), object: outputPaths.get(i).map(URL.init(fileURLWithPath:)), // for now - swift-dependencies are not requested in the driver compilation mode swiftDependencies: nil ) return new }) } } private func generateForEmitModule( config: XCRemoteCacheConfig, target: String, moduleName: String ) throws -> SwiftcContext { guard outputPaths.count == 1 else { throw SwiftFrontendArgInputError.emitModulOuputCountIsNot1(parsed: outputPaths.count) } guard let objcHeaderOutput = objcHeaderOutput else { throw SwiftFrontendArgInputError.emitModuleMissingObjcHeaderPath } guard diagnosticsPaths.count <= 1 else { throw SwiftFrontendArgInputError.emitModuleDiagnosticsOuputCountIsHigherThan1( parsed: diagnosticsPaths.count ) } guard dependenciesPaths.count <= 1 else { throw SwiftFrontendArgInputError.emitModuleDependenciesOuputCountIsHigherThan1( parsed: dependenciesPaths.count ) } let steps: SwiftcContext.SwiftcSteps = SwiftcContext.SwiftcSteps( compileFilesScope: .none, emitModule: SwiftcContext.SwiftcStepEmitModule( objcHeaderOutput: URL(fileURLWithPath: objcHeaderOutput), modulePathOutput: URL(fileURLWithPath: outputPaths[0]), dependencies: dependenciesPaths.first.map(URL.init(fileURLWithPath:)) ) ) return try .init( config: config, moduleName: moduleName, steps: steps, inputs: .map([:]), target: target, compilationFiles: .list(inputPaths), exampleWorkspaceFilePath: objcHeaderOutput ) } func generateSwiftcContext(config: XCRemoteCacheConfig) throws -> SwiftcContext { guard compile != emitModule else { throw SwiftFrontendArgInputError.bothCompilationAndEmitAction } let inputPathsCount = inputPaths.count guard inputPathsCount > 0 else { throw SwiftFrontendArgInputError.noCompilationInputs } guard let target = target else { throw SwiftFrontendArgInputError.emitMissingTarget } guard let moduleName = moduleName else { throw SwiftFrontendArgInputError.emitMissingModuleName } if compile { return try generateForCompilation( config: config, target: target, moduleName: moduleName ) } else { return try generateForEmitModule( config: config, target: target, moduleName: moduleName ) } } }