New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Custom File Name for .xcodeproj
at Model Level
#462
Changes from all commits
051ee9a
f92e139
6e5e0e1
4d32b07
97ba5c2
21a78b2
3fc7f8f
a12b226
366dd0b
6500e5e
8b9ba90
0087ac4
84adc50
434ce16
4f7d0d3
2baba03
284e710
365569c
2f8282d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Foundation | ||
|
||
public struct TemplateString: Encodable, Decodable, Equatable { | ||
/// Contains a string that can be interpolated with options. | ||
let rawString: String | ||
} | ||
|
||
extension TemplateString: ExpressibleByStringLiteral { | ||
public init(stringLiteral: String) { | ||
rawString = stringLiteral | ||
} | ||
} | ||
|
||
extension TemplateString: CustomStringConvertible { | ||
public var description: String { | ||
return rawString | ||
} | ||
} | ||
|
||
extension TemplateString: ExpressibleByStringInterpolation { | ||
public init(stringInterpolation: StringInterpolation) { | ||
rawString = stringInterpolation.string | ||
} | ||
|
||
public struct StringInterpolation: StringInterpolationProtocol { | ||
var string: String | ||
|
||
public init(literalCapacity _: Int, interpolationCount _: Int) { | ||
string = String() | ||
} | ||
|
||
public mutating func appendLiteral(_ literal: String) { | ||
string.append(literal) | ||
} | ||
|
||
public mutating func appendInterpolation(_ token: TemplateString.Token) { | ||
string.append(token.rawValue) | ||
} | ||
} | ||
} | ||
|
||
extension TemplateString { | ||
/// Provides a template for existing project properties. | ||
/// | ||
/// - projectName: The name of the project. | ||
public enum Token: String { | ||
case projectName = "${project_name}" | ||
} | ||
} | ||
|
||
public func == (lhs: TemplateString.Token, rhs: TemplateString.Token) -> Bool { | ||
switch (lhs, rhs) { | ||
case (.projectName, .projectName): | ||
return true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,74 @@ | ||
import Foundation | ||
|
||
/// This model allows to configure Tuist. | ||
public class TuistConfig: Codable { | ||
public class TuistConfig: Encodable, Decodable, Equatable { | ||
/// Contains options related to the project generation. | ||
/// | ||
/// - generateManifestElement: When passed, Tuist generates the projects, targets and schemes to compile the project manifest. | ||
public enum GenerationOption: String, Codable { | ||
/// - xcodeProjectName(TemplateString): When passed, Tuist generates the project with the specific name on disk instead of using the project name. | ||
public enum GenerationOptions: Encodable, Decodable, Equatable { | ||
case generateManifest | ||
case xcodeProjectName(TemplateString) | ||
} | ||
|
||
/// Generation options. | ||
public let generationOptions: [GenerationOption] | ||
public let generationOptions: [GenerationOptions] | ||
|
||
/// Initializes the tuist cofiguration. | ||
/// | ||
/// - Parameter generationOptions: Generation options. | ||
public init(generationOptions: [GenerationOption]) { | ||
public init(generationOptions: [GenerationOptions]) { | ||
self.generationOptions = generationOptions | ||
dumpIfNeeded(self) | ||
} | ||
} | ||
|
||
extension TuistConfig.GenerationOptions { | ||
enum CodingKeys: String, CodingKey { | ||
case generateManifest | ||
case xcodeProjectName | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
if container.allKeys.contains(.generateManifest), try container.decodeNil(forKey: .generateManifest) == false { | ||
self = .generateManifest | ||
return | ||
} | ||
if container.allKeys.contains(.xcodeProjectName), try container.decodeNil(forKey: .xcodeProjectName) == false { | ||
var associatedValues = try container.nestedUnkeyedContainer(forKey: .xcodeProjectName) | ||
let templateProjectName = try associatedValues.decode(TemplateString.self) | ||
self = .xcodeProjectName(templateProjectName) | ||
return | ||
} | ||
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case")) | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
|
||
switch self { | ||
case .generateManifest: | ||
_ = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .generateManifest) | ||
case let .xcodeProjectName(templateProjectName): | ||
var associatedValues = container.nestedUnkeyedContainer(forKey: .xcodeProjectName) | ||
try associatedValues.encode(templateProjectName) | ||
} | ||
} | ||
} | ||
|
||
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 (.generateManifest, .generateManifest): | ||
return true | ||
case let (.xcodeProjectName(lhs), .xcodeProjectName(rhs)): | ||
return lhs.rawString == rhs.rawString | ||
default: return false | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,24 +53,18 @@ class GeneratorModelLoader: GeneratorModelLoading { | |
/// - Throws: Error encountered during the loading process (e.g. Missing project) | ||
func loadProject(at path: AbsolutePath) throws -> TuistGenerator.Project { | ||
let manifest = try manifestLoader.loadProject(at: path) | ||
let tuistConfig = try loadTuistConfig(at: path) | ||
|
||
try manifestLinter.lint(project: manifest) | ||
.printAndThrowIfNeeded(printer: printer) | ||
|
||
let project = try TuistGenerator.Project.from(manifest: manifest, | ||
path: path, | ||
fileHandler: fileHandler, | ||
printer: printer) | ||
let tuistConfig = try loadTuistConfig(at: path) | ||
printer: printer, | ||
tuistConfig: tuistConfig) | ||
|
||
if let manifestTargetGenerator = manifestTargetGenerator, tuistConfig.generationOptions.contains(.generateManifest) { | ||
let manifestTarget = try manifestTargetGenerator.generateManifestTarget(for: project.name, | ||
at: path) | ||
return project.adding(target: manifestTarget) | ||
|
||
} else { | ||
return project | ||
} | ||
return try enriched(model: project, with: tuistConfig) | ||
} | ||
|
||
func loadWorkspace(at path: AbsolutePath) throws -> TuistGenerator.Workspace { | ||
|
@@ -114,6 +108,42 @@ class GeneratorModelLoader: GeneratorModelLoading { | |
return locateDirectoryTraversingParents(from: from.parentDirectory, path: path, fileHandler: fileHandler) | ||
} | ||
} | ||
|
||
private func enriched(model: TuistGenerator.Project, | ||
with config: TuistGenerator.TuistConfig) throws -> TuistGenerator.Project { | ||
var enrichedModel = model | ||
|
||
// Manifest target | ||
if let manifestTargetGenerator = manifestTargetGenerator, config.generationOptions.contains(.generateManifest) { | ||
let manifestTarget = try manifestTargetGenerator.generateManifestTarget(for: enrichedModel.name, | ||
at: enrichedModel.path) | ||
enrichedModel = enrichedModel.adding(target: manifestTarget) | ||
} | ||
|
||
// Xcode project file name | ||
let xcodeFileName = xcodeFileNameOverride(from: config, for: model) | ||
enrichedModel = enrichedModel.replacing(fileName: xcodeFileName) | ||
|
||
return enrichedModel | ||
} | ||
|
||
private func xcodeFileNameOverride(from config: TuistGenerator.TuistConfig, | ||
for model: TuistGenerator.Project) -> String? { | ||
var xcodeFileName = config.generationOptions.compactMap { item -> String? in | ||
switch item { | ||
case let .xcodeProjectName(projectName): | ||
return projectName.description | ||
default: | ||
return nil | ||
} | ||
}.first | ||
|
||
let projectNameTemplate = TemplateString.Token.projectName.rawValue | ||
xcodeFileName = xcodeFileName?.replacingOccurrences(of: projectNameTemplate, | ||
with: model.name) | ||
|
||
return xcodeFileName | ||
} | ||
} | ||
|
||
extension TuistGenerator.TuistConfig { | ||
|
@@ -125,11 +155,13 @@ extension TuistGenerator.TuistConfig { | |
} | ||
|
||
extension TuistGenerator.TuistConfig.GenerationOption { | ||
static func from(manifest: ProjectDescription.TuistConfig.GenerationOption, | ||
static func from(manifest: ProjectDescription.TuistConfig.GenerationOptions, | ||
path _: AbsolutePath) throws -> TuistGenerator.TuistConfig.GenerationOption { | ||
switch manifest { | ||
case .generateManifest: | ||
return .generateManifest | ||
case let .xcodeProjectName(templateString): | ||
return .xcodeProjectName(templateString.description) | ||
} | ||
} | ||
} | ||
|
@@ -218,7 +250,8 @@ extension TuistGenerator.Project { | |
static func from(manifest: ProjectDescription.Project, | ||
path: AbsolutePath, | ||
fileHandler: FileHandling, | ||
printer: Printing) throws -> TuistGenerator.Project { | ||
printer: Printing, | ||
tuistConfig _: TuistGenerator.TuistConfig) throws -> TuistGenerator.Project { | ||
let name = manifest.name | ||
let settings = manifest.settings.map { TuistGenerator.Settings.from(manifest: $0, path: path) } | ||
let targets = try manifest.targets.map { | ||
|
@@ -249,12 +282,24 @@ extension TuistGenerator.Project { | |
func adding(target: TuistGenerator.Target) -> TuistGenerator.Project { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this is related to my previous comment about |
||
return Project(path: path, | ||
name: name, | ||
fileName: fileName, | ||
settings: settings, | ||
filesGroup: filesGroup, | ||
targets: targets + [target], | ||
schemes: schemes, | ||
additionalFiles: additionalFiles) | ||
} | ||
|
||
func replacing(fileName: String?) -> TuistGenerator.Project { | ||
return Project(path: path, | ||
name: name, | ||
fileName: fileName, | ||
settings: settings, | ||
filesGroup: filesGroup, | ||
targets: targets, | ||
schemes: schemes, | ||
additionalFiles: additionalFiles) | ||
} | ||
} | ||
|
||
extension TuistGenerator.Target { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import Foundation | ||
import XCTest | ||
@testable import ProjectDescription | ||
|
||
final class TuistConfigTests: XCTestCase { | ||
func test_tuistconfig_toJSON() throws { | ||
let tuistConfig = TuistConfig(generationOptions: | ||
[.generateManifest, | ||
.xcodeProjectName("someprefix-\(.projectName)")]) | ||
|
||
XCTAssertCodable(tuistConfig) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that the model being passed is a reference, so creating another internal variable does not make any copy. I'd just remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do the following operations in the same method:
The idea was to be consistent and continue returning a new model every time a
Project
property is changed.Project
's properties are immutable.The alternative would be to change the
let
s insideProject
tovar
s.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it like that for now. If we see maintaining those methods that create copies of the instances become cumbersome, we can reconsider the approach.