Merge pull request #2095 from tuist/plugins/add-plugin-manifest
Project Plugins: Add Plugin.swift manifest.
This commit is contained in:
commit
11b611983b
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: [
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import XCTest
|
||||
@testable import ProjectDescription
|
||||
|
||||
final class PluginTests: XCTestCase {
|
||||
func test_codable() throws {
|
||||
let subject = Plugin(name: "TestPlugin")
|
||||
XCTAssertCodable(subject)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue