Sources/xcprepare/XCPrepareMain.swift (183 lines of code) (raw):

// Copyright (c) 2021 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 ArgumentParser import Foundation import XCRemoteCache enum XCPrepareMainError: Error { case missingPlatform case missingConfiguration } /// Actions supported by xc-prepare enum XCPrepareAction: String, ExpressibleByArgument { /// Setups RemoteCache session: finds the best commit sha to used by Remote Cache case prepare /// Marks the current commit to indicate succesful artifacts generation case mark } /// Extra command that should be called after each merge with primary branch /// It fetches the most common commit sha on the /// primary repo and finds a historical commit that has artifact on the remote server /// Selected sha is saved as a file to let other commands know, which commit artifact is being used /// To not introduce unnecessary Xcode steps invalidation, `mdate` of that file is set to a commit date /// If the 'prepare' action succeds, it prints selected sha to the standard output struct XCPrepareMain: ParsableCommand { private static func nonEmptyString(_ value: String) throws -> String { guard !value.isEmpty else { throw ValidationError("Unsupported empty string argument") } return value } private static func allCasesMessage<T: CaseIterable & RawRepresentable>(_ type: T.Type) -> String where T.RawValue == String { T.allCases.map(\.rawValue).map { "'\($0)'" } .joined(separator: ", ") } private static func toCase<T: RawRepresentable & CaseIterable>(_ value: String) throws -> T where T.RawValue == String { guard let mode = T(rawValue: value) else { throw ValidationError("Non supported value. Supported: \(allCasesMessage(T.self))") } return mode } static var configuration = CommandConfiguration( abstract: "Manage XCRemoteCache session.", subcommands: [Prepare.self, Mark.self, Offline.self, Stats.self, Config.self, Integrate.self], defaultSubcommand: Prepare.self ) struct Prepare: ParsableCommand { static var configuration = CommandConfiguration(abstract: "Find the latest commit sha for the Remote Cache.") @Option(help: "Build configuration") var configuration: [String] @Option(help: "Build Platform") var platform: [String] @Option(help: "Custom Xcode Build Number") var xcode: String? @Option(default: .yaml, help: "Output format") var format: XCOutputFormat func run() throws { guard !configuration.isEmpty else { throw XCPrepareMainError.missingConfiguration } guard !platform.isEmpty else { throw XCPrepareMainError.missingPlatform } let mode = XCPrepareMode.online( configurations: configuration, platforms: platform, customXcodeBuildNumber: xcode ) XCPrepare(mode, format: format).main() } } struct Offline: ParsableCommand { static var configuration = CommandConfiguration( abstract: """ Offline mode - optimistically use the latest sha from the primary branch and first remote cache address. """) @Option(default: .yaml, help: "Output format") var format: XCOutputFormat func run() throws { XCPrepare(.offline, format: format).main() } } struct Mark: ParsableCommand { static var configuration = CommandConfiguration(abstract: "Mark current sha as XCRemoteCache-ready.") @Option(help: "Build configuration") var configuration: String @Option(help: "Build Platform") var platform: String @Option(help: "Custom Xcode Build Number") var xcode: String? @Option(help: "Custom commit to mark") var commit: String? func run() throws { XCPrepareMark(configuration: configuration, platform: platform, xcode: xcode, commit: commit).main() } } struct Config: ParsableCommand { static var configuration = CommandConfiguration(abstract: "Print the XCRemoteCache configuration") @Option(default: .yaml, help: "Output format") var format: XCOutputFormat func run() throws { XCConfig(format: format).main() } } struct Stats: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Offline mode - optimistically use the latest sha from the primary branch" ) @Flag(help: "Resets") var reset: Bool @Option(default: .yaml, help: "Output format") var format: XCOutputFormat func run() throws { XCStats(format: format, reset: reset).main() } } struct Integrate: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Integrate XCRemoteCache into existing .xcodeproj" ) @Option(help: ".xcodeproj location") var input: String @Option(help: "XCRemoteCache mode. Supported values: \(allCasesMessage(Mode.self))", transform: toCase) var mode: Mode @Option(default: "", help: "comma separated list of targets to integrate XCRemoteCache.") var targetsInclude: String @Option(default: "", help: """ comma separated list of targets to not integrate XCRemoteCache. \ Takes priority over --targets-include. """) var targetsExclude: String @Option(default: "", help: "comma separated list of configurations to integrate XCRemoteCache.") var configurationsInclude: String @Option(default: "Release", help: """ comma separated list of configurations to not integrate XCRemoteCache. \ Takes priority over --configurations-include. """) var configurationsExclude: String @Option(help: """ [Producer only] The final target that generates cache artifacts. Once this target is finished, \ no other targets are allowed to upload artifacts to the remote server for a given sha, \ configuration and platform context. """) var finalProducerTarget: String? @Option(default: "Debug", help: """ comma delimited list of configurations that need to have all artifacts \ uploaded to the remote site before using given sha. """, transform: nonEmptyString) var consumerEligibleConfigurations: String @Option(default: "iphonesimulator", help: """ comma delimited list of platforms that need to have all artifacts \ uploaded to the remote site before using given sha. """, transform: nonEmptyString) var consumerEligiblePlatforms: String @Option(help: "Save the project with integrated XCRemoteCache to a separate location") var output: String? @Option( default: .user, help: """ LLDBInit mode. Appends to .lldbinit a command required for debugging. \ Supported values: \(allCasesMessage(LLDBInitMode.self)) """, transform: toCase ) var lldbInit: LLDBInitMode @Option( default: "/\(String(repeating: "x", count: 10))", help: """ An arbitrary source location shared between producers and consumers. \ Should be unique for a project. """, transform: nonEmptyString ) var fakeSrcRoot: String @Option(name: .customLong("sdks-exclude"), default: "", help: """ comma separated list of sdks to not integrate XCRemoteCache (e.g. "watchos*, watchsimulator*") """, transform: nonEmptyString) var sdksExclude: String func run() throws { XCIntegrate( input: input, mode: mode, configurationsExclude: configurationsExclude, configurationsInclude: configurationsInclude, targetsExclude: targetsExclude, targetsInclude: targetsInclude, finalProducerTarget: finalProducerTarget, consumerEligibleConfigurations: consumerEligibleConfigurations, consumerEligiblePlatforms: consumerEligiblePlatforms, lldbMode: lldbInit, fakeSrcRoot: fakeSrcRoot, sdksExclude: sdksExclude, output: output ).main() } } }