Bugfix/templates (#1600)

* Use name of template instead of Template.swift.

* Rename Template.swift in rest of documetnation.

* Fix tests.

* Add documentation for fileName method.

* Update changelog.

* Reformat code.

* Fix CachedManifestLoaderTests.
This commit is contained in:
Marek Fořt 2020-08-06 23:07:19 +02:00 committed by GitHub
parent 3bd6a4950a
commit fce1fc2b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 140 additions and 66 deletions

View File

@ -6,6 +6,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
### Fixed
- Fix name collision when having multiple templates [#1600](https://github.com/tuist/tuist/pull/1600) by [@fortmarek](https://github.com/fortmarek)
- Allow to cache and warm static frameworks too (only dynamic frameworks were cached before) [#1590](https://github.com/tuist/tuist/pull/1590) by [@RomainBoulay](https://github.com/RomainBoulay)
- Add support for `.xctest` dependency for tvOS targets [#1597](https://github.com/tuist/tuist/pull/1597) by [@kwridan](https://github.com/kwridan).

View File

@ -21,10 +21,10 @@ public struct Template: Codable, Equatable {
/// Enum containing information about how to generate file
public enum Contents: Codable, Equatable {
/// String Contents is defined in `Template.swift` and contains a simple `String`
/// String Contents is defined in `name_of_template.swift` and contains a simple `String`
/// Can not contain any additional logic apart from plain `String` from `arguments`
case string(String)
/// File content is defined in a different file from `Template.swift`
/// File content is defined in a different file from `name_of_template.swift`
/// Can contain additional logic and anything that is defined in `ProjectDescriptionHelpers`
case file(Path)

View File

@ -115,7 +115,7 @@ public class CachedManifestLoader: ManifestLoading {
}
private func findManifestPath(for manifest: Manifest, at path: AbsolutePath) -> AbsolutePath? {
let manifestFileNames = [manifest.fileName, manifest.deprecatedFileName]
let manifestFileNames = [manifest.fileName(path), manifest.deprecatedFileName]
return manifestFileNames
.compactMap { $0 }
.map { path.appending(component: $0) }

View File

@ -48,7 +48,7 @@ extension GeneratorModelLoader: GeneratorModelLoading {
public func loadConfig(at path: AbsolutePath) throws -> TuistCore.Config {
// If the Config.swift file exists in the root Tuist/ directory, we load it from there
if let rootDirectoryPath = rootDirectoryLocator.locate(from: path) {
let configPath = rootDirectoryPath.appending(RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName)"))
let configPath = rootDirectoryPath.appending(RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName(path))"))
if FileHandler.shared.exists(configPath) {
let manifest = try manifestLoader.loadConfig(at: configPath.parentDirectory)
@ -58,7 +58,7 @@ extension GeneratorModelLoader: GeneratorModelLoading {
// We first try to load the deprecated file. If it doesn't exist, we load the new file name.
let fileNames = [Manifest.config]
.flatMap { [$0.deprecatedFileName, $0.fileName] }
.flatMap { [$0.deprecatedFileName, $0.fileName(path)] }
.compactMap { $0 }
for fileName in fileNames {

View File

@ -22,9 +22,9 @@ public enum ManifestLoaderError: FatalError, Equatable {
case let .unexpectedOutput(path):
return "Unexpected output trying to parse the manifest at path \(path.pathString)"
case let .manifestNotFound(manifest, path):
return "\(manifest?.fileName ?? "Manifest") not found at path \(path.pathString)"
return "\(manifest?.fileName(path) ?? "Manifest") not found at path \(path.pathString)"
case let .manifestCachingFailed(manifest, path):
return "Could not cache \(manifest?.fileName ?? "Manifest") at path \(path.pathString)"
return "Could not cache \(manifest?.fileName(path) ?? "Manifest") at path \(path.pathString)"
}
}
@ -77,9 +77,9 @@ public protocol ManifestLoading {
/// - Parameter path: Path to the directory that contains the Setup.swift.
func loadSetup(at path: AbsolutePath) throws -> [Upping]
/// Loads the Template.swift in the given directory.
/// Loads the name_of_template.swift in the given directory.
/// - Parameters:
/// - path: Path to the directory that contains the Template.swift
/// - path: Path to the directory that contains the name_of_template.swift
func loadTemplate(at path: AbsolutePath) throws -> ProjectDescription.Template
/// List all the manifests in the given directory.
@ -137,7 +137,7 @@ public class ManifestLoader: ManifestLoading {
}
public func loadSetup(at path: AbsolutePath) throws -> [Upping] {
let setupPath = path.appending(component: Manifest.setup.fileName)
let setupPath = path.appending(component: Manifest.setup.fileName(path))
guard FileHandler.shared.exists(setupPath) else {
throw ManifestLoaderError.manifestNotFound(.setup, path)
}
@ -154,7 +154,7 @@ public class ManifestLoader: ManifestLoading {
// MARK: - Private
private func loadManifest<T: Decodable>(_ manifest: Manifest, at path: AbsolutePath) throws -> T {
var fileNames = [manifest.fileName]
var fileNames = [manifest.fileName(path)]
if let deprecatedFileName = manifest.deprecatedFileName {
fileNames.insert(deprecatedFileName, at: 0)
}

View File

@ -62,7 +62,7 @@ public class SetupLoader: SetupLoading {
/// or if there isn't a `Setup.swift` file within the project path.
public func meet(at path: AbsolutePath) throws {
let path = try lookupManifest(from: path)
logger.info("Setting up the environment defined in \(path.appending(component: Manifest.setup.fileName).pathString)")
logger.info("Setting up the environment defined in \(path.appending(component: Manifest.setup.fileName(path)).pathString)")
let setup = try manifestLoader.loadSetup(at: path)
try setup.map { command in upLinter.lint(up: command) }
.flatMap { $0 }
@ -78,7 +78,7 @@ public class SetupLoader: SetupLoading {
/// It traverses up the directory hierarchy until it finds a Setup.swift file.
/// - Parameter path: Path from where to do the lookup.
private func lookupManifest(from path: AbsolutePath) throws -> AbsolutePath {
let manfiestPath = path.appending(component: Manifest.setup.fileName)
let manfiestPath = path.appending(component: Manifest.setup.fileName(path))
if FileHandler.shared.exists(manfiestPath) {
return path
} else if path != .root {

View File

@ -7,7 +7,7 @@ import TuistSupport
public protocol TemplateLoading {
/// Load `TuistScaffold.Template` at given `path`
/// - Parameters:
/// - path: Path of template manifest file `Template.swift`
/// - path: Path of template manifest file `name_of_template.swift`
/// - Returns: Loaded `TuistScaffold.Template`
func loadTemplate(at path: AbsolutePath) throws -> TuistCore.Template
}

View File

@ -1,4 +1,5 @@
import Foundation
import TSCBasic
public enum Manifest: CaseIterable {
case project
@ -13,12 +14,17 @@ public enum Manifest: CaseIterable {
switch self {
case .config:
return "TuistConfig.swift"
case .template:
return "Template.swift"
default:
return nil
}
}
public var fileName: String {
/// - Parameters:
/// - path: Path to the folder that contains the manifest
/// - Returns: File name of the `Manifest`
public func fileName(_ path: AbsolutePath) -> String {
switch self {
case .project:
return "Project.swift"
@ -29,7 +35,7 @@ public enum Manifest: CaseIterable {
case .setup:
return "Setup.swift"
case .template:
return "Template.swift"
return "\(path.basenameWithoutExt).swift"
case .galaxy:
return "Galaxy.swift"
}

View File

@ -25,7 +25,7 @@ public final class ManifestFilesLocator: ManifestFilesLocating {
public func locate(at: AbsolutePath) -> [(Manifest, AbsolutePath)] {
Manifest.allCases.compactMap { manifest in
let path = at.appending(component: manifest.fileName)
let path = at.appending(component: manifest.fileName(at))
if FileHandler.shared.exists(path) { return (manifest, path) }
return nil
}
@ -33,8 +33,8 @@ public final class ManifestFilesLocator: ManifestFilesLocating {
public func locateAll(at: AbsolutePath) -> [(Manifest, AbsolutePath)] {
guard let rootPath = rootDirectoryLocator.locate(from: at) else { return locate(at: at) }
let projectsPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.project.fileName)").map { (Manifest.project, $0) }
let workspacesPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.workspace.fileName)").map { (Manifest.workspace, $0) }
let projectsPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.project.fileName(at))").map { (Manifest.project, $0) }
let workspacesPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.workspace.fileName(at))").map { (Manifest.workspace, $0) }
return projectsPaths + workspacesPaths
}
}

View File

@ -55,7 +55,7 @@ let template = Template(
templatePath: templatePath("Config.stencil")),
.file(path: ".gitignore",
templatePath: templatePath("Gitignore.stencil")),
.file(path: "Tuist/Templates/framework/Template.swift",
.file(path: "Tuist/Templates/framework/framework.swift",
templatePath: templatePath("ExampleTemplate.stencil")),
.file(path: "Tuist/Templates/framework/project.stencil",
templatePath: templatePath("ExampleProject")),

View File

@ -27,7 +27,7 @@ final class ManifestLoaderTests: TuistTestCase {
let config = Config(generationOptions: [])
"""
let manifestPath = temporaryPath.appending(component: Manifest.config.fileName)
let manifestPath = temporaryPath.appending(component: Manifest.config.fileName(temporaryPath))
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)
@ -44,7 +44,7 @@ final class ManifestLoaderTests: TuistTestCase {
let project = Project(name: "tuist")
"""
let manifestPath = temporaryPath.appending(component: Manifest.project.fileName)
let manifestPath = temporaryPath.appending(component: Manifest.project.fileName(temporaryPath))
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)
@ -64,7 +64,7 @@ final class ManifestLoaderTests: TuistTestCase {
let workspace = Workspace(name: "tuist", projects: [])
"""
let manifestPath = temporaryPath.appending(component: Manifest.workspace.fileName)
let manifestPath = temporaryPath.appending(component: Manifest.workspace.fileName(temporaryPath))
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)
@ -86,7 +86,7 @@ final class ManifestLoaderTests: TuistTestCase {
])
"""
let manifestPath = temporaryPath.appending(component: Manifest.setup.fileName)
let manifestPath = temporaryPath.appending(component: Manifest.setup.fileName(temporaryPath))
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)
@ -102,7 +102,7 @@ final class ManifestLoaderTests: TuistTestCase {
XCTAssertEqual(customUp?.isMet, ["c"])
}
func test_loadTemplate() throws {
func test_loadDeprecatedTemplate() throws {
// Given
let temporaryPath = try self.temporaryPath()
let content = """
@ -125,6 +125,30 @@ final class ManifestLoaderTests: TuistTestCase {
XCTAssertEqual(got.description, "Template description")
}
func test_loadTemplate() throws {
// Given
let temporaryPath = try self.temporaryPath().appending(component: "folder")
try fileHandler.createFolder(temporaryPath)
let content = """
import ProjectDescription
let template = Template(
description: "Template description"
)
"""
let manifestPath = temporaryPath.appending(component: "folder.swift")
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)
// When
let got = try subject.loadTemplate(at: temporaryPath)
// Then
XCTAssertEqual(got.description, "Template description")
}
func test_load_invalidFormat() throws {
// Given
let temporaryPath = try self.temporaryPath()
@ -133,7 +157,7 @@ final class ManifestLoaderTests: TuistTestCase {
let project
"""
let manifestPath = temporaryPath.appending(component: Manifest.project.fileName)
let manifestPath = temporaryPath.appending(component: Manifest.project.fileName(temporaryPath))
try content.write(to: manifestPath.url,
atomically: true,
encoding: .utf8)

View File

@ -245,7 +245,7 @@ final class CachedManifestLoaderTests: TuistUnitTestCase {
private func stub(manifest: Project,
at path: AbsolutePath) throws
{
let manifestPath = path.appending(component: Manifest.project.fileName)
let manifestPath = path.appending(component: Manifest.project.fileName(path))
try fileHandler.touch(manifestPath)
let manifestData = try JSONEncoder().encode(manifest)
try fileHandler.write(String(data: manifestData, encoding: .utf8)!, path: manifestPath, atomically: true)
@ -255,7 +255,7 @@ final class CachedManifestLoaderTests: TuistUnitTestCase {
private func stub(deprecatedManifest manifest: Config,
at path: AbsolutePath) throws
{
let manifestPath = path.appending(component: Manifest.config.deprecatedFileName ?? Manifest.config.fileName)
let manifestPath = path.appending(component: Manifest.config.deprecatedFileName ?? Manifest.config.fileName(path))
try fileHandler.touch(manifestPath)
let manifestData = try JSONEncoder().encode(manifest)
try fileHandler.write(String(data: manifestData, encoding: .utf8)!, path: manifestPath, atomically: true)

View File

@ -255,7 +255,7 @@ final class RecursiveManifestLoaderTests: TuistUnitTestCase {
{
let manifestPath = path
.appending(relativePath)
.appending(component: Manifest.project.fileName)
.appending(component: Manifest.project.fileName(path.appending(relativePath)))
try fileHandler.touch(manifestPath)
projectManifests[manifestPath.parentDirectory] = manifest
}
@ -265,7 +265,7 @@ final class RecursiveManifestLoaderTests: TuistUnitTestCase {
{
let manifestPath = path
.appending(relativePath)
.appending(component: Manifest.workspace.fileName)
.appending(component: Manifest.workspace.fileName(path.appending(relativePath)))
try fileHandler.touch(manifestPath)
workspaceManifests[manifestPath.parentDirectory] = manifest
}

View File

@ -39,7 +39,7 @@ final class SetupLoaderTests: TuistUnitTestCase {
func test_meet_when_no_actions() throws {
// given
let projectPath = try temporaryPath()
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName))
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName(projectPath)))
var receivedPaths = [String]()
manifestLoader.loadSetupStub = { gotPath in
receivedPaths.append(gotPath.pathString)
@ -56,7 +56,7 @@ final class SetupLoaderTests: TuistUnitTestCase {
func test_meet_when_actions_provided() throws {
// given
let projectPath = try temporaryPath()
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName))
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName(projectPath)))
let mockUp1 = MockUp(name: "1")
mockUp1.isMetStub = { _ in true }
@ -82,7 +82,7 @@ final class SetupLoaderTests: TuistUnitTestCase {
// given
let temporaryPath = try self.temporaryPath()
let projectPath = temporaryPath.appending(component: "Project")
try FileHandler.shared.touch(temporaryPath.appending(component: Manifest.setup.fileName))
try FileHandler.shared.touch(temporaryPath.appending(component: Manifest.setup.fileName(projectPath)))
let mockUp1 = MockUp(name: "1")
mockUp1.isMetStub = { _ in true }
@ -107,7 +107,7 @@ final class SetupLoaderTests: TuistUnitTestCase {
func test_meet_when_loadSetup_throws() throws {
// given
let projectPath = try temporaryPath()
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName))
try FileHandler.shared.touch(projectPath.appending(component: Manifest.setup.fileName(projectPath)))
manifestLoader.loadSetupStub = { _ in throw ManifestLoaderError.manifestNotFound(.setup, projectPath) }
// when / then

View File

@ -5,12 +5,14 @@ import XCTest
@testable import TuistSupportTesting
final class ManifestTests: TuistUnitTestCase {
func test_fileName() {
XCTAssertEqual(Manifest.project.fileName, "Project.swift")
XCTAssertEqual(Manifest.workspace.fileName, "Workspace.swift")
XCTAssertEqual(Manifest.config.fileName, "Config.swift")
XCTAssertEqual(Manifest.setup.fileName, "Setup.swift")
XCTAssertEqual(Manifest.galaxy.fileName, "Galaxy.swift")
func test_fileName() throws {
let temporaryPath = try self.temporaryPath().appending(component: "folder")
XCTAssertEqual(Manifest.project.fileName(temporaryPath), "Project.swift")
XCTAssertEqual(Manifest.workspace.fileName(temporaryPath), "Workspace.swift")
XCTAssertEqual(Manifest.config.fileName(temporaryPath), "Config.swift")
XCTAssertEqual(Manifest.setup.fileName(temporaryPath), "Setup.swift")
XCTAssertEqual(Manifest.galaxy.fileName(temporaryPath), "Galaxy.swift")
XCTAssertEqual(Manifest.template.fileName(temporaryPath), "folder.swift")
}
func test_deprecatedFileName() {

View File

@ -1,13 +1,24 @@
Feature: Scaffold a project using Tuist
Scenario: The project is an application with templates (ios_app_with_templates)
Given that tuist is available
Given that tuist is available
And I have a working directory
Then I copy the fixture ios_app_with_templates into the working directory
Then tuist scaffolds a custom template to TemplateProject named TemplateProject
Then content of a file named TemplateProject/custom.swift in a directory TemplateProject should be equal to // this is test TemplateProject content
Then content of a file named TemplateProject/generated.swift in a directory TemplateProject should be equal to // Generated file with platform: ios and name: TemplateProject
Then content of a file named TemplateProject/generated.swift in a directory TemplateProject should be equal to:
"""
// Generated file with platform: ios and name: TemplateProject
"""
# Uses new naming where name of template file is no longer `Template.swift` but `name_of_template.swift`
Then tuist scaffolds a custom_two template to TemplateProject named TemplateProject
Then content of a file named TemplateProject/custom.swift in a directory TemplateProject should be equal to // this is test TemplateProject content
Then content of a file named TemplateProject/generated.swift in a directory TemplateProject should be equal to:
"""
// Generated file with platform: ios and name: TemplateProject
"""
Scenario: The project is a just initialized project
Given that tuist is available
And I have a working directory
@ -15,4 +26,4 @@ Feature: Scaffold a project using Tuist
And tuist scaffolds a framework template to Projects/ named MyFeature
When tuist generates the project at Projects/MyFeature
Then I should be able to build for iOS the scheme MyFeature
Then I should be able to test for iOS the scheme MyFeature
Then I should be able to test for iOS the scheme MyFeature

View File

@ -7,3 +7,7 @@ end
Then(/content of a file named (.+) in a directory (.+) should be equal to (.+)/) do |file, dir, content|
assert_equal File.read(File.join(@dir, dir, file)), content
end
Then(/content of a file named (.+) in a directory (.+) should be equal to:$/) do |file, dir, content|
assert_equal File.read(File.join(@dir, dir, file)), content
end

View File

@ -2,23 +2,28 @@ import ProjectDescription
let project = Project(name: "App",
targets: [
Target(name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.App",
infoPlist: "Support/Info.plist",
sources: ["Sources/**"],
resources: [
/* Path to resouces can be defined here */
// "Resources/**"
],
Target(name: "AppTests",
platform: .iOS,
product: .unitTests,
bundleId: "io.tuist.AppTests",
infoPlist: "Support/Tests.plist",
sources: "Tests/**",
dependencies: [
.target(name: "App")
])
])
Target(
name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.App",
infoPlist: "Support/Info.plist",
sources: ["Sources/**"],
resources: [
/* Path to resouces can be defined here */
// "Resources/**"
]
),
Target(
name: "AppTests",
platform: .iOS,
product: .unitTests,
bundleId: "io.tuist.AppTests",
infoPlist: "Support/Tests.plist",
sources: "Tests/**",
dependencies: [
.target(name: "App")
]
)
]
)

View File

@ -1 +1 @@
// Generated file with platform: {{ platform }} and name: {{ name }}
// Generated file with platform: {{ platform }} and name: {{ name }}

View File

@ -0,0 +1,20 @@
import ProjectDescription
let nameAttributeTwo: Template.Attribute = .required("name")
let platformAttributeTwo: Template.Attribute = .optional("platform", default: "ios")
let testContentsTwo = """
// this is test \(nameAttributeTwo) content
"""
let templateTwo = Template(
description: "Custom template",
attributes: [
nameAttributeTwo,
platformAttributeTwo
],
files: [
.string(path: "\(nameAttributeTwo)/custom.swift", contents: testContentsTwo),
.file(path: "\(nameAttributeTwo)/generated.swift", templatePath: "platform_two.stencil"),
]
)

View File

@ -0,0 +1 @@
// Generated file with platform: {{ platform }} and name: {{ name }}

View File

@ -15,7 +15,7 @@ Tuist is not opinionated about the content of your templates, and what you use t
## Defining a template
To define templates, you can run `tuist edit` and then create a directory under `Tuist/Templates` that represents your template. Templates need a manifest file, `Template.swift` that describes the template:
To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. Templates need a manifest file, `name_of_template.swift` that describes the template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this:
```swift
import ProjectDescription