Merge pull request #2095 from tuist/plugins/add-plugin-manifest

Project Plugins: Add Plugin.swift manifest.
This commit is contained in:
Pedro Piñera Buendía 2020-12-03 09:56:15 +01:00 committed by GitHub
commit 11b611983b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 216 additions and 60 deletions

View File

@ -5,21 +5,20 @@ public typealias TuistConfig = Config
/// This model allows to configure Tuist.
public struct Config: Codable, Equatable {
/// Contains options related to the project generation.
///
/// - xcodeProjectName(TemplateString): When passed, Tuist generates the project with the specific name on disk instead of using the project name.
/// - organizationName(String): When passed, Tuist generates the project with the specific organization name.
/// - developmentRegion(String): When passed, Tuist generates the project with the specific development region.
/// - disableAutogeneratedSchemes: When passed, Tuist generates the project only with custom specified schemes, autogenerated default schemes are skipped
/// - disableSynthesizedResourceAccessors: When passed, Tuist does not synthesize resource accessors
/// - disableShowEnvironmentVarsInScriptPhases: When passed, Tuist disables echoing the ENV in shell script build phases
/// - enableCodeCoverage: When passed, Tuist will enable code coverage for autogenerated default schemes
public enum GenerationOptions: Encodable, Decodable, Equatable {
public enum GenerationOptions: Codable, Equatable {
/// Tuist generates the project with the specific name on disk instead of using the project name.
case xcodeProjectName(TemplateString)
/// Tuist generates the project with the specific organization name.
case organizationName(String)
/// Tuist generates the project with the specific development region.
case developmentRegion(String)
/// Tuist generates the project only with custom specified schemes, autogenerated default
case disableAutogeneratedSchemes
/// Tuist does not synthesize resource accessors
case disableSynthesizedResourceAccessors
/// Tuist disables echoing the ENV in shell script build phases
case disableShowEnvironmentVarsInScriptPhases
/// When passed, Tuist will enable code coverage for autogenerated default schemes
case enableCodeCoverage
}
@ -29,6 +28,9 @@ public struct Config: Codable, Equatable {
/// List of Xcode versions that the project supports.
public let compatibleXcodeVersions: CompatibleXcodeVersions
/// List of `Plugin`s used to extend Tuist.
public let plugins: [PluginLocation]
/// Cloud configuration.
public let cloud: Cloud?
@ -37,12 +39,16 @@ public struct Config: Codable, Equatable {
/// - Parameters:
/// - compatibleXcodeVersions: List of Xcode versions the project is compatible with.
/// - cloud: Cloud configuration.
/// - plugins: A list of plugins to extend Tuist.
/// - generationOptions: List of options to use when generating the project.
public init(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
cloud: Cloud? = nil,
generationOptions: [GenerationOptions])
{
public init(
compatibleXcodeVersions: CompatibleXcodeVersions = .all,
cloud: Cloud? = nil,
plugins: [PluginLocation] = [],
generationOptions: [GenerationOptions]
) {
self.compatibleXcodeVersions = compatibleXcodeVersions
self.plugins = plugins
self.generationOptions = generationOptions
self.cloud = cloud
dumpIfNeeded(self)
@ -67,38 +73,29 @@ extension Config.GenerationOptions {
var associatedValues = try container.nestedUnkeyedContainer(forKey: .xcodeProjectName)
let templateProjectName = try associatedValues.decode(TemplateString.self)
self = .xcodeProjectName(templateProjectName)
return
}
if container.allKeys.contains(.organizationName), try container.decodeNil(forKey: .organizationName) == false {
} else if container.allKeys.contains(.organizationName), try container.decodeNil(forKey: .organizationName) == false {
var associatedValues = try container.nestedUnkeyedContainer(forKey: .organizationName)
let organizationName = try associatedValues.decode(String.self)
self = .organizationName(organizationName)
return
}
if container.allKeys.contains(.developmentRegion), try container.decodeNil(forKey: .developmentRegion) == false {
} else if container.allKeys.contains(.developmentRegion), try container.decodeNil(forKey: .developmentRegion) == false {
var associatedValues = try container.nestedUnkeyedContainer(forKey: .developmentRegion)
let developmentRegion = try associatedValues.decode(String.self)
self = .developmentRegion(developmentRegion)
return
}
if container.allKeys.contains(.disableAutogeneratedSchemes), try container.decode(Bool.self, forKey: .disableAutogeneratedSchemes) {
} else if container.allKeys.contains(.disableAutogeneratedSchemes), try container.decode(Bool.self, forKey: .disableAutogeneratedSchemes) {
self = .disableAutogeneratedSchemes
return
}
if container.allKeys.contains(.disableSynthesizedResourceAccessors), try container.decode(Bool.self, forKey: .disableSynthesizedResourceAccessors) {
} else if container.allKeys.contains(.disableSynthesizedResourceAccessors),
try container.decode(Bool.self, forKey: .disableSynthesizedResourceAccessors)
{
self = .disableSynthesizedResourceAccessors
return
}
if container.allKeys.contains(.disableShowEnvironmentVarsInScriptPhases), try container.decode(Bool.self, forKey: .disableShowEnvironmentVarsInScriptPhases) {
} else if container.allKeys.contains(.disableShowEnvironmentVarsInScriptPhases),
try container.decode(Bool.self, forKey: .disableShowEnvironmentVarsInScriptPhases)
{
self = .disableShowEnvironmentVarsInScriptPhases
return
}
if container.allKeys.contains(.enableCodeCoverage), try container.decode(Bool.self, forKey: .enableCodeCoverage) {
} else if container.allKeys.contains(.enableCodeCoverage), try container.decode(Bool.self, forKey: .enableCodeCoverage) {
self = .enableCodeCoverage
return
} else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
}
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
}
public func encode(to encoder: Encoder) throws {
@ -125,29 +122,3 @@ extension Config.GenerationOptions {
}
}
}
public func == (lhs: TuistConfig, rhs: TuistConfig) -> Bool {
guard lhs.generationOptions == rhs.generationOptions else { return false }
return true
}
public func == (lhs: TuistConfig.GenerationOptions, rhs: TuistConfig.GenerationOptions) -> Bool {
switch (lhs, rhs) {
case let (.xcodeProjectName(lhs), .xcodeProjectName(rhs)):
return lhs.rawString == rhs.rawString
case let (.organizationName(lhs), .organizationName(rhs)):
return lhs == rhs
case let (.developmentRegion(lhs), .developmentRegion(rhs)):
return lhs == rhs
case (.disableAutogeneratedSchemes, .disableAutogeneratedSchemes):
return true
case (.disableSynthesizedResourceAccessors, .disableSynthesizedResourceAccessors):
return true
case (.disableShowEnvironmentVarsInScriptPhases, .disableShowEnvironmentVarsInScriptPhases):
return true
case (.enableCodeCoverage, .enableCodeCoverage):
return true
default:
return false
}
}

View File

@ -0,0 +1,20 @@
import Foundation
/// A `Plugin` manifest allows for defining extensions for Tuist.
///
/// Supported plugins include:
/// - ProjectDescriptionHelpers
/// - These are plugins designed to be usable by any other manifest excluding `Config` and `Plugin`.
/// - The source files for these helpers must live under a ProjectDescriptionHelpers directory in the location where `Plugin` manifest lives.
///
public struct Plugin: Codable, Equatable {
/// The name of the `Plugin`.
public let name: String
/// Creates a `Plugin`.
/// - Parameters:
/// - name: The name of the plugin.
public init(name: String) {
self.name = name
}
}

View File

@ -0,0 +1,118 @@
import Foundation
/// The location to a directory containing a `Plugin` manifest.
public struct PluginLocation: Codable, Equatable {
/// The type of location `local` or `git`.
public let type: LocationType
/// A `Path` to a directory containing a `Plugin` manifest.
///
/// Example:
/// ```
/// .local(path: "/User/local/bin")
/// ```
public static func local(path: Path) -> Self {
PluginLocation(type: .local(path: path))
}
/// A `URL` to a `git` repository pointing at a `branch`.
///
/// Example:
/// ```
/// .git(url: "https://git/plugin.git", branch: "main")
/// ```
public static func git(url: String, branch: String) -> Self {
PluginLocation(type: .gitWithBranch(url: url, branch: branch))
}
/// A `URL` to a `git` repository pointing at a `tag`.
///
/// Example:
/// ```
/// .git(url: "https://git/plugin.git", tag: "1.0.0")
/// ```
public static func git(url: String, tag: String) -> Self {
PluginLocation(type: .gitWithTag(url: url, tag: tag))
}
/// A `URL` to a `git` repository pointing at a commit `sha`.
///
/// Example:
/// ```
/// .git(url: "https://git/plugin.git", sha: "d06b4b3d")
/// ```
public static func git(url: String, sha: String) -> Self {
PluginLocation(type: .gitWithSha(url: url, sha: sha))
}
}
// MARK: - Codable
extension PluginLocation {
public enum LocationType: Codable, Equatable {
case local(path: Path)
case gitWithBranch(url: String, branch: String)
case gitWithTag(url: String, tag: String)
case gitWithSha(url: String, sha: String)
enum CodingKeys: CodingKey {
case local
case gitWithBranch
case gitWithTag
case gitWithSha
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .local(path):
try container.encode(path, forKey: .local)
case let .gitWithBranch(url, branch):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .gitWithBranch)
try nestedContainer.encode(url)
try nestedContainer.encode(branch)
case let .gitWithTag(url, tag):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .gitWithTag)
try nestedContainer.encode(url)
try nestedContainer.encode(tag)
case let .gitWithSha(url, sha):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .gitWithSha)
try nestedContainer.encode(url)
try nestedContainer.encode(sha)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let key = container.allKeys.first
switch key {
case .local:
let path = try container.decode(Path.self, forKey: .local)
self = .local(path: path)
case .gitWithBranch:
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .gitWithBranch)
let url = try nestedContainer.decode(String.self)
let branch = try nestedContainer.decode(String.self)
self = .gitWithBranch(url: url, branch: branch)
case .gitWithTag:
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .gitWithTag)
let url = try nestedContainer.decode(String.self)
let tag = try nestedContainer.decode(String.self)
self = .gitWithTag(url: url, tag: tag)
case .gitWithSha:
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .gitWithSha)
let url = try nestedContainer.decode(String.self)
let sha = try nestedContainer.decode(String.self)
self = .gitWithSha(url: url, sha: sha)
case .none:
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Unable to decode `LocationType`. \(String(describing: key)) is an unexpected key."
)
)
}
}
}
}

View File

@ -18,6 +18,20 @@ final class ConfigTests: XCTestCase {
XCTAssertCodable(config)
}
func test_config_toJSON_WITH_gitPlugin() {
let config = Config(plugins: [.git(url: "https://git.com/repo.git", branch: "main")],
generationOptions: [])
XCTAssertCodable(config)
}
func test_config_toJSON_WITH_localPlugin() {
let config = Config(plugins: [.local(path: "/some/path/to/plugin")],
generationOptions: [])
XCTAssertCodable(config)
}
func test_config_toJSON_withAutogeneratedSchemes() throws {
let config = Config(cloud: Cloud(url: "https://cloud.tuist.io", projectId: "123", options: [.insights]),
generationOptions: [

View File

@ -0,0 +1,24 @@
import XCTest
@testable import ProjectDescription
final class PluginLocationTests: XCTestCase {
func test_codable_local() throws {
let subject = PluginLocation.local(path: .init("/some/path"))
XCTAssertCodable(subject)
}
func test_codable_gitWithBranch() throws {
let subject = PluginLocation.git(url: "https://git.com/repo.git", branch: "main")
XCTAssertCodable(subject)
}
func test_codable_gitWithTag() throws {
let subject = PluginLocation.git(url: "https://git.com/repo.git", tag: "1.0.0")
XCTAssertCodable(subject)
}
func test_codable_gitWithSha() throws {
let subject = PluginLocation.git(url: "https://git.com/repo.git", sha: "64d8d24f")
XCTAssertCodable(subject)
}
}

View File

@ -0,0 +1,9 @@
import XCTest
@testable import ProjectDescription
final class PluginTests: XCTestCase {
func test_codable() throws {
let subject = Plugin(name: "TestPlugin")
XCTAssertCodable(subject)
}
}