Adding support for project additional files (#314)

Part of https://github.com/tuist/tuist/issues/202

### Short description

Often projects may include additional files or folder references that don't need to be part of any build phase (e.g. design references, documentation etc...)


### Solution

`Workspace.swift` manifest now supports `additionalFiles`, the same concept can be added to the `Project.swift` manifest with the same rules.

- `.glob(pattern: "Documentation/**")`: glob pattern to one or more files
- `.folderReference(path: "Designs")`: single path to a directory that is added as a folder reference

Example:

```swift
let project = Project(name: "App",
                      targets: [
                           // ...
                      ],
                      additionalFiles: [
                         "Dangerfile.swift"
                         "Documentation/**",
                         .folderReference(path: "Designs")
                     ])
```

### Implementation 

- [x] Update `Project` manifest to include `additionalFiles`
- [x] Update models to translate globs to paths
- [x] Update `ProjectFileElements` to include `additionalFiles`
- [x] Include fixture
- [x] Update documentation
- [x] Update changelog

### Test Plan 

- Run `tuist generate` within `fixtures/ios_app_with_custom_workspace/App`
- Open the generated workspace
- Verify `Dangerfile.swift` is included in the project but isn't part of the resource phase
- Verify `Documentation` files are included (with the appropriate group structure that reflects the file system) and that the files aren't copied to resources
- Verify `Server` folder reference is included
This commit is contained in:
Kas 2019-04-03 09:43:23 +01:00 committed by GitHub
parent 2726304e29
commit 8d7f15f8d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 354 additions and 173 deletions

View File

@ -6,6 +6,9 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
### Changed
### Added
- Adding support for project additional files https://github.com/tuist/tuist/pull/314 by @kwridan
### Removed
### Fixed

View File

@ -0,0 +1,67 @@
import Foundation
/// File element
///
/// - glob: a glob pattern for files to include
/// - folderReference: a single path to a directory
///
/// Note: For convenience, an element can be represented as a string literal
/// `"some/pattern/**"` is the equivalent of `FileElement.glob(pattern: "some/pattern/**")`
public enum FileElement: Codable {
/// A glob pattern of files to include
case glob(pattern: String)
/// Relative path to a directory to include
/// as a folder reference
case folderReference(path: String)
private enum TypeName: String, Codable {
case glob
case folderReference
}
private var typeName: TypeName {
switch self {
case .glob:
return .glob
case .folderReference:
return .folderReference
}
}
public enum CodingKeys: String, CodingKey {
case type
case pattern
case path
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TypeName.self, forKey: .type)
switch type {
case .glob:
let pattern = try container.decode(String.self, forKey: .pattern)
self = .glob(pattern: pattern)
case .folderReference:
let path = try container.decode(String.self, forKey: .path)
self = .folderReference(path: path)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(typeName, forKey: .type)
switch self {
case let .glob(pattern: pattern):
try container.encode(pattern, forKey: .pattern)
case let .folderReference(path: path):
try container.encode(path, forKey: .path)
}
}
}
extension FileElement: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .glob(pattern: value)
}
}

View File

@ -6,19 +6,16 @@ public class Project: Codable {
public let name: String
public let targets: [Target]
public let settings: Settings?
public enum CodingKeys: String, CodingKey {
case name
case targets
case settings
}
public let additionalFiles: [FileElement]
public init(name: String,
settings: Settings? = nil,
targets: [Target] = []) {
targets: [Target] = [],
additionalFiles: [FileElement] = []) {
self.name = name
self.targets = targets
self.settings = settings
self.additionalFiles = additionalFiles
dumpIfNeeded(self)
}
}

View File

@ -10,7 +10,7 @@ public class Workspace: Codable {
public let projects: [String]
/// List of files to include in the workspace (e.g. Documentation)
public let additionalFiles: [Element]
public let additionalFiles: [FileElement]
/// Workspace
///
@ -20,71 +20,10 @@ public class Workspace: Codable {
/// - name: Name of the workspace.
/// - projects: List of project relative paths (or glob patterns) to generate and include.
/// - additionalFiles: List of files to include in the workspace (e.g. Documentation)
public init(name: String, projects: [String], additionalFiles: [Element] = []) {
public init(name: String, projects: [String], additionalFiles: [FileElement] = []) {
self.name = name
self.projects = projects
self.additionalFiles = additionalFiles
dumpIfNeeded(self)
}
}
extension Workspace {
public enum Element: Codable {
/// A glob pattern of files to include
case glob(pattern: String)
/// Relative path to a directory to include
/// as a folder reference
case folderReference(path: String)
private enum TypeName: String, Codable {
case glob
case folderReference
}
private var typeName: TypeName {
switch self {
case .glob:
return .glob
case .folderReference:
return .folderReference
}
}
public enum CodingKeys: String, CodingKey {
case type
case pattern
case path
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TypeName.self, forKey: .type)
switch type {
case .glob:
let pattern = try container.decode(String.self, forKey: .pattern)
self = .glob(pattern: pattern)
case .folderReference:
let path = try container.decode(String.self, forKey: .path)
self = .folderReference(path: path)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(typeName, forKey: .type)
switch self {
case let .glob(pattern: pattern):
try container.encode(pattern, forKey: .pattern)
case let .folderReference(path: path):
try container.encode(path, forKey: .path)
}
}
}
}
extension Workspace.Element: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .glob(pattern: value)
}
}

View File

@ -81,7 +81,7 @@ public class Generator: Generating {
let workspace = Workspace(name: project.name,
projects: graph.projectPaths,
additionalFiles: workspaceFiles.map(Workspace.Element.file))
additionalFiles: workspaceFiles.map(FileElement.file))
return try workspaceGenerator.generate(workspace: workspace,
path: path,

View File

@ -5,9 +5,16 @@ import xcodeproj
// swiftlint:disable:next type_body_length
class ProjectFileElements {
struct FileElement: Hashable {
struct GroupFileElement: Hashable {
var path: AbsolutePath
var group: ProjectGroup
var isReference: Bool
init(path: AbsolutePath, group: ProjectGroup, isReference: Bool = false) {
self.path = path
self.group = group
self.isReference = isReference
}
}
// MARK: - Static
@ -41,24 +48,21 @@ class ProjectFileElements {
groups: ProjectGroups,
pbxproj: PBXProj,
sourceRootPath: AbsolutePath) throws {
var files = Set<FileElement>()
var files = Set<GroupFileElement>()
var products = Set<String>()
project.targets.forEach { target in
let targetFilePaths = targetFiles(target: target)
let targetFileElements = targetFilePaths.map {
FileElement(path: $0, group: target.filesGroup)
GroupFileElement(path: $0, group: target.filesGroup)
}
files.formUnion(targetFileElements)
products.formUnion(targetProducts(target: target))
}
let projectFilePaths = projectFiles(project: project)
let projectFileElements = projectFilePaths.map {
FileElement(path: $0, group: project.filesGroup)
}
let projectFileElements = projectFiles(project: project)
files.formUnion(projectFileElements)
let pathsSort = filesSortener.sort
let filesSort: (FileElement, FileElement) -> Bool = {
let filesSort: (GroupFileElement, GroupFileElement) -> Bool = {
pathsSort($0.path, $1.path)
}
@ -90,17 +94,27 @@ class ProjectFileElements {
filesGroup: project.filesGroup)
}
func projectFiles(project: Project) -> Set<AbsolutePath> {
var files = Set<AbsolutePath>()
func projectFiles(project: Project) -> Set<GroupFileElement> {
var fileElements = Set<GroupFileElement>()
/// Config files
if let debugConfigFile = project.settings?.debug?.xcconfig {
files.insert(debugConfigFile)
}
if let releaseConfigFile = project.settings?.release?.xcconfig {
files.insert(releaseConfigFile)
}
return files
let configFiles = [
project.settings?.debug?.xcconfig,
project.settings?.release?.xcconfig,
].compactMap { $0 }
fileElements.formUnion(configFiles.map {
GroupFileElement(path: $0, group: project.filesGroup)
})
// Additional files
fileElements.formUnion(project.additionalFiles.map {
GroupFileElement(path: $0.path,
group: project.filesGroup,
isReference: $0.isReference)
})
return fileElements
}
func targetProducts(target: Target) -> Set<String> {
@ -141,7 +155,7 @@ class ProjectFileElements {
return files
}
func generate(files: [FileElement],
func generate(files: [GroupFileElement],
groups: ProjectGroups,
pbxproj: PBXProj,
sourceRootPath: AbsolutePath) throws {
@ -212,8 +226,8 @@ class ProjectFileElements {
self.products[productName] = fileReference
} else if let precompiledNode = node as? PrecompiledNode {
let fileElement = FileElement(path: precompiledNode.path,
group: filesGroup)
let fileElement = GroupFileElement(path: precompiledNode.path,
group: filesGroup)
try generate(fileElement: fileElement,
groups: groups,
pbxproj: pbxproj,
@ -222,7 +236,7 @@ class ProjectFileElements {
}
}
func generate(fileElement: FileElement,
func generate(fileElement: GroupFileElement,
groups: ProjectGroups,
pbxproj: PBXProj,
sourceRootPath: AbsolutePath) throws {
@ -239,6 +253,7 @@ class ProjectFileElements {
group = try groups.projectGroup(named: groupName)
}
guard let firstElement = addElement(relativePath: closestRelativeRelativePath,
isReference: fileElement.isReference,
from: sourceRootPath,
toGroup: group,
pbxproj: pbxproj) else {
@ -256,6 +271,7 @@ class ProjectFileElements {
for component in fileElement.path.relative(to: lastPath).components {
if lastGroup == nil { return }
guard let element = addElement(relativePath: RelativePath(component),
isReference: fileElement.isReference,
from: lastPath,
toGroup: lastGroup!,
pbxproj: pbxproj) else {
@ -269,6 +285,7 @@ class ProjectFileElements {
// MARK: - Internal
@discardableResult func addElement(relativePath: RelativePath,
isReference: Bool,
from: AbsolutePath,
toGroup: PBXGroup,
pbxproj: PBXProj) -> (element: PBXFileElement, path: AbsolutePath)? {
@ -300,7 +317,7 @@ class ProjectFileElements {
name: name,
toGroup: toGroup,
pbxproj: pbxproj)
} else if isGroup(path: absolutePath) {
} else if isGroup(path: absolutePath), !isReference {
return addGroupElement(from: from,
folderAbsolutePath: absolutePath,
folderRelativePath: relativePath,
@ -387,7 +404,7 @@ class ProjectFileElements {
name: String?,
toGroup: PBXGroup,
pbxproj: PBXProj) {
let lastKnownFileType = Xcode.filetype(extension: fileAbsolutePath.extension!)
let lastKnownFileType = fileAbsolutePath.extension.flatMap { Xcode.filetype(extension: $0) }
let file = PBXFileReference(sourceTree: .group, name: name, lastKnownFileType: lastKnownFileType, path: fileRelativePath.asString)
pbxproj.add(object: file)
toGroup.children.append(file)

View File

@ -55,7 +55,7 @@ private class DirectoryStructure {
let fileHandler: FileHandling
let projects: [AbsolutePath]
let files: [Workspace.Element]
let files: [FileElement]
private let containers: [String] = [
".playground",
@ -65,7 +65,7 @@ private class DirectoryStructure {
init(path: AbsolutePath,
fileHandler: FileHandling,
projects: [AbsolutePath],
files: [Workspace.Element]) {
files: [FileElement]) {
self.path = path
self.fileHandler = fileHandler
self.projects = projects
@ -100,7 +100,7 @@ private class DirectoryStructure {
return root
}
private func fileNode(from element: Workspace.Element) -> Node {
private func fileNode(from element: FileElement) -> Node {
switch element {
case let .file(path: path):
return .file(path)
@ -113,7 +113,7 @@ private class DirectoryStructure {
return .project(path)
}
private func isFileOrFolderReference(element: Workspace.Element) -> Bool {
private func isFileOrFolderReference(element: FileElement) -> Bool {
switch element {
case .folderReference:
return true

View File

@ -0,0 +1,25 @@
import Basic
import Foundation
public enum FileElement: Equatable {
case file(path: AbsolutePath)
case folderReference(path: AbsolutePath)
var path: AbsolutePath {
switch self {
case let .file(path):
return path
case let .folderReference(path):
return path
}
}
var isReference: Bool {
switch self {
case .file:
return false
case .folderReference:
return true
}
}
}

View File

@ -20,6 +20,9 @@ public class Project: Equatable, CustomStringConvertible {
/// The group to place project files within
public let filesGroup: ProjectGroup
/// Additional files to include in the project
public let additionalFiles: [FileElement]
// MARK: - Init
/// Initializes the project with its attributes.
@ -27,17 +30,23 @@ public class Project: Equatable, CustomStringConvertible {
/// - Parameters:
/// - path: Path to the folder that contains the project manifest.
/// - name: Project name.
/// - targets: Project settings.
/// - settings: The settings to apply at the project level
/// - filesGroup: The root group to place project files within
/// - targets: The project targets
/// - additionalFiles: The additional files to include in the project
/// *(Those won't be included in any build phases)*
public init(path: AbsolutePath,
name: String,
settings: Settings? = nil,
filesGroup: ProjectGroup,
targets: [Target]) {
targets: [Target],
additionalFiles: [FileElement] = []) {
self.path = path
self.name = name
self.targets = targets
self.settings = settings
self.filesGroup = filesGroup
self.additionalFiles = additionalFiles
}
// MARK: - Init

View File

@ -7,11 +7,11 @@ public class Workspace: Equatable {
public let name: String
public let projects: [AbsolutePath]
public let additionalFiles: [Element]
public let additionalFiles: [FileElement]
// MARK: - Init
public init(name: String, projects: [AbsolutePath], additionalFiles: [Element] = []) {
public init(name: String, projects: [AbsolutePath], additionalFiles: [FileElement] = []) {
self.name = name
self.projects = projects
self.additionalFiles = additionalFiles
@ -43,19 +43,3 @@ extension Workspace {
additionalFiles: additionalFiles)
}
}
extension Workspace {
public enum Element: Equatable {
case file(path: AbsolutePath)
case folderReference(path: AbsolutePath)
var path: AbsolutePath {
switch self {
case let .file(path):
return path
case let .folderReference(path):
return path
}
}
}
}

View File

@ -43,7 +43,10 @@ class GeneratorModelLoader: GeneratorModelLoading {
func loadProject(at path: AbsolutePath) throws -> TuistGenerator.Project {
let manifest = try manifestLoader.loadProject(at: path)
let project = try TuistGenerator.Project.from(manifest: manifest, path: path, fileHandler: fileHandler)
let project = try TuistGenerator.Project.from(manifest: manifest,
path: path,
fileHandler: fileHandler,
printer: printer)
let manifestTarget = try manifestTargetGenerator.generateManifestTarget(for: project.name,
at: path)
@ -68,16 +71,6 @@ extension TuistGenerator.Workspace {
fileHandler: FileHandling,
manifestLoader: GraphManifestLoading,
printer: Printing) throws -> TuistGenerator.Workspace {
func globFiles(_ string: String) -> [AbsolutePath] {
let files = fileHandler.glob(path, glob: string)
if files.isEmpty {
printer.print(warning: "No files found at: \(string)")
}
return files
}
func globProjects(_ string: String) -> [AbsolutePath] {
let projects = fileHandler.glob(path, glob: string)
.lazy
@ -93,6 +86,34 @@ extension TuistGenerator.Workspace {
return Array(projects)
}
let additionalFiles = manifest.additionalFiles.flatMap {
TuistGenerator.FileElement.from(manifest: $0,
path: path,
fileHandler: fileHandler,
printer: printer)
}
return TuistGenerator.Workspace(name: manifest.name,
projects: manifest.projects.flatMap(globProjects),
additionalFiles: additionalFiles)
}
}
extension TuistGenerator.FileElement {
static func from(manifest: ProjectDescription.FileElement,
path: AbsolutePath,
fileHandler: FileHandling,
printer: Printing) -> [TuistGenerator.FileElement] {
func globFiles(_ string: String) -> [AbsolutePath] {
let files = fileHandler.glob(path, glob: string)
if files.isEmpty {
printer.print(warning: "No files found at: \(string)")
}
return files
}
func folderReferences(_ relativePath: String) -> [AbsolutePath] {
let folderReferencePath = path.appending(RelativePath(relativePath))
@ -109,34 +130,37 @@ extension TuistGenerator.Workspace {
return [folderReferencePath]
}
func workspaceElement(from element: ProjectDescription.Workspace.Element) -> [TuistGenerator.Workspace.Element] {
switch element {
case let .glob(pattern: pattern):
return globFiles(pattern).map(Workspace.Element.file)
case let .folderReference(path: folderReferencePath):
return folderReferences(folderReferencePath).map(Workspace.Element.folderReference)
}
switch manifest {
case let .glob(pattern: pattern):
return globFiles(pattern).map(FileElement.file)
case let .folderReference(path: folderReferencePath):
return folderReferences(folderReferencePath).map(FileElement.folderReference)
}
return TuistGenerator.Workspace(name: manifest.name,
projects: manifest.projects.flatMap(globProjects),
additionalFiles: manifest.additionalFiles.flatMap(workspaceElement))
}
}
extension TuistGenerator.Project {
static func from(manifest: ProjectDescription.Project,
path: AbsolutePath,
fileHandler: FileHandling) throws -> TuistGenerator.Project {
fileHandler: FileHandling,
printer: Printing) 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 { try TuistGenerator.Target.from(manifest: $0, path: path, fileHandler: fileHandler) }
let additionalFiles = manifest.additionalFiles.flatMap {
TuistGenerator.FileElement.from(manifest: $0,
path: path,
fileHandler: fileHandler,
printer: printer)
}
return Project(path: path,
name: name,
settings: settings,
filesGroup: .group(name: "Project"),
targets: targets)
targets: targets,
additionalFiles: additionalFiles)
}
func adding(target: TuistGenerator.Target) -> TuistGenerator.Project {
@ -144,7 +168,8 @@ extension TuistGenerator.Project {
name: name,
settings: settings,
filesGroup: filesGroup,
targets: targets + [target])
targets: targets + [target],
additionalFiles: additionalFiles)
}
}

View File

@ -6,7 +6,7 @@ import XCTest
@testable import TuistGenerator
final class ProjectFileElementsTests: XCTestCase {
typealias FileElement = ProjectFileElements.FileElement
typealias FileElement = ProjectFileElements.GroupFileElement
var subject: ProjectFileElements!
var fileHandler: MockFileHandler!
@ -20,16 +20,27 @@ final class ProjectFileElementsTests: XCTestCase {
}
func test_projectFiles() {
let settings = Settings(base: [:],
debug: Configuration(settings: [:],
xcconfig: AbsolutePath("/project/debug.xcconfig")),
release: Configuration(settings: [:],
xcconfig: AbsolutePath("/project/release.xcconfig")))
// Given
let settings = Settings(debug: Configuration(xcconfig: AbsolutePath("/project/debug.xcconfig")),
release: Configuration(xcconfig: AbsolutePath("/project/release.xcconfig")))
let project = Project.test(path: AbsolutePath("/project/"), settings: settings)
let project = Project.test(path: AbsolutePath("/project/"),
settings: settings,
additionalFiles: [
.file(path: "/path/to/file"),
.folderReference(path: "/path/to/folder"),
])
// When
let files = subject.projectFiles(project: project)
XCTAssertTrue(files.contains(AbsolutePath("/project/debug.xcconfig")))
XCTAssertTrue(files.contains(AbsolutePath("/project/release.xcconfig")))
// Then
XCTAssertTrue(files.isSuperset(of: [
FileElement(path: "/project/debug.xcconfig", group: project.filesGroup),
FileElement(path: "/project/release.xcconfig", group: project.filesGroup),
FileElement(path: "/path/to/file", group: project.filesGroup),
FileElement(path: "/path/to/folder", group: project.filesGroup, isReference: true),
]))
}
func test_targetProducts() {

View File

@ -43,7 +43,7 @@ final class WorkspaceGeneratorTests: XCTestCase {
"Website/about.html",
])
let additionalFiles: [Workspace.Element] = [
let additionalFiles: [FileElement] = [
.file(path: path.appending(RelativePath("README.md"))),
.file(path: path.appending(RelativePath("Documentation/README.md"))),
.folderReference(path: path.appending(RelativePath("Website"))),

View File

@ -94,7 +94,7 @@ class WorkspaceStructureGeneratorTests: XCTestCase {
"/path/to/workspace/README.md",
])
let additionalFiles: [Workspace.Element] = [
let additionalFiles: [FileElement] = [
.folderReference(path: "/path/to/workspace/Documentation/Guides"),
.folderReference(path: "/path/to/workspace/Documentation/Proposals"),
.file(path: "/path/to/workspace/README.md"),
@ -200,7 +200,7 @@ class WorkspaceStructureGeneratorTests: XCTestCase {
"/path/to/workspace/Modules/A",
])
let files: [Workspace.Element] = [
let files: [FileElement] = [
.folderReference(path: "/path/to/workspace/Modules/A"),
]
let workspace = Workspace.test(projects: projects,

View File

@ -7,11 +7,13 @@ extension Project {
name: String = "Project",
settings: Settings? = Settings.test(),
filesGroup: ProjectGroup = .group(name: "Project"),
targets: [Target] = [Target.test()]) -> Project {
targets: [Target] = [Target.test()],
additionalFiles: [FileElement] = []) -> Project {
return Project(path: path,
name: name,
settings: settings,
filesGroup: filesGroup,
targets: targets)
targets: targets,
additionalFiles: additionalFiles)
}
}

View File

@ -5,7 +5,7 @@ import Foundation
extension Workspace {
static func test(name: String = "test",
projects: [AbsolutePath] = [],
additionalFiles: [Workspace.Element] = []) -> Workspace {
additionalFiles: [FileElement] = []) -> Workspace {
return Workspace(name: name,
projects: projects,
additionalFiles: additionalFiles)

View File

@ -64,7 +64,7 @@ final class DumpCommandTests: XCTestCase {
encoding: .utf8)
let result = try parser.parse([DumpCommand.command, "-p", tmpDir.path.asString])
try subject.run(with: result)
let expected = "{\n \"name\": \"tuist\",\n \"targets\": [\n\n ]\n}\n"
let expected = "{\n \"additionalFiles\": [\n\n ],\n \"name\": \"tuist\",\n \"targets\": [\n\n ]\n}\n"
XCTAssertEqual(printer.printArgs.first, expected)
}
}

View File

@ -89,6 +89,57 @@ class GeneratorModelLoaderTest: XCTestCase {
XCTAssertEqual(model.targets[2].name, "Project-Manifest")
}
func test_loadProject_withAdditionalFiles() throws {
// Given
let files = try createFiles([
"Documentation/README.md",
"Documentation/guide.md",
])
let manifests = [
path: ProjectManifest.test(name: "SomeProject",
additionalFiles: [
"Documentation/**/*.md",
]),
]
let manifestLoader = createManifestLoader(with: manifests)
let subject = GeneratorModelLoader(fileHandler: fileHandler,
manifestLoader: manifestLoader,
manifestTargetGenerator: manifestTargetGenerator)
// When
let model = try subject.loadProject(at: path)
// Then
XCTAssertEqual(model.additionalFiles, files.map { .file(path: $0) })
}
func test_loadProject_withFolderReferences() throws {
// Given
let files = try createFolders([
"Stubs",
])
let manifests = [
path: ProjectManifest.test(name: "SomeProject",
additionalFiles: [
.folderReference(path: "Stubs"),
]),
]
let manifestLoader = createManifestLoader(with: manifests)
let subject = GeneratorModelLoader(fileHandler: fileHandler,
manifestLoader: manifestLoader,
manifestTargetGenerator: manifestTargetGenerator)
// When
let model = try subject.loadProject(at: path)
// Then
XCTAssertEqual(model.additionalFiles, files.map { .folderReference(path: $0) })
}
func test_loadWorkspace() throws {
// Given
let manifests = [

View File

@ -4,7 +4,7 @@ import Foundation
extension Workspace {
static func test(name: String = "Workspace",
projects: [String] = [],
additionalFiles: [Element] = []) -> Workspace {
additionalFiles: [FileElement] = []) -> Workspace {
return Workspace(name: name,
projects: projects,
additionalFiles: additionalFiles)
@ -14,10 +14,12 @@ extension Workspace {
extension Project {
static func test(name: String = "Project",
settings: Settings? = nil,
targets: [Target] = []) -> Project {
targets: [Target] = [],
additionalFiles: [FileElement] = []) -> Project {
return Project(name: name,
settings: settings,
targets: targets)
targets: targets,
additionalFiles: additionalFiles)
}
}

View File

@ -21,7 +21,12 @@ let project = Project(name: "MyProject",
.project(target: "Framework1", path: "../Framework1"),
.project(target: "Framework2", path: "../Framework2"),
])
])
],
additionalFiles: [
"Dangerfile.swift",
"Documentation/**",
.folderReference(path: "Website")
])
```
### Project
@ -31,6 +36,7 @@ A `Project.swift` should initialize a variable of type `Project`. It can take an
- **Name:** Name of the project. It's used to determine the name of the generated Xcode project.
- **Targets:** It contains the list of targets that belong to the project. Read more [about targets](#target)
- **Settings (optional):** Read more about [settings](#settings)
- **AdditionalFiles (optional):**: List of [FileElement](#FileElement)s to include in the project - these won't be included in any of the build phases.
> **Note:** All the relative paths in the project manifest are relative to the folder that contains the manifest file.
@ -96,6 +102,15 @@ A `Settings` object contains an optional dictionary with build settings and rela
- **Debug (optional):** The debug configuration settings. The settings are initialized with `.settings([String: String], xcconfig: String)` where the first argument are the build settings and the second a relative path to an xcconfig file. Both arguments are optionals.
- **Release (optional):** Same as debug but for the release configuration.
### FileElement
A `FileElement` can be one of the following options:
- `.glob(pattern: String)`: A file path (or glob pattern) to include.
- *For convenience, a string literal can be used as an alternate way to specify this option.*
- `.folderReference(path: String)`: A directory path to include as a folder reference.
## Workspace.swift
By default, `tuist generate` generates an Xcode workspace that has the same name as the current project. It includes the project and all its dependencies. Tuist allows customizing this behaviour by defining a workspace manifest within a `Workspace.swift` file.
@ -128,9 +143,6 @@ A `Workspace.swift` should initialize a variable of type `Workspace`. It can tak
- **Name:** Name of the workspace. Its used to determine the name of the generated Xcode workspace.
- **Projects:** List of paths (or glob patterns) to projects to generate and include within the generated Xcode workspace.
- **AdditionalFiles:**: List of one of the following options
- `.glob(pattern: String)`: A file path (or glob pattern) to include in the workspace.
- *For convenience, a string literal can be used as an alternate way to specify this option.*
- `.folderReference(path: String)`: A directory path to include as a folder reference in the workspace.
- **AdditionalFiles (optional):**: List of [FileElement](#FileElement)s to include in the workspace - these won't be included in any of the projects or their build phases.
>***Note:***All the relative paths in the workspace manifest are relative to the folder that contains the manifest file.*

View File

@ -14,8 +14,14 @@ Contains a few projects and a `Workspace.swift` manifest file.
The workspace manifest defines:
- glob patterns to list projects
- glob patterns to list additional files
- folder references
- glob patterns to include documentation files
- folder reference to directory with html files
The App's project manifest leverages `additionalFiles` tha defines:
- glob patterns to include documentation files
- Includes a swift `Danger.swift` file that shouldn't get included in any buid phase
- folder references to a directory with json files
## ios_app_with_tests

View File

@ -0,0 +1,6 @@
import Danger
// This should fail to build if it
// accidentally gets added to the project
// sources build phases

View File

@ -0,0 +1,3 @@
# Item

View File

@ -0,0 +1,3 @@
# List

View File

@ -0,0 +1,3 @@
# App Documentation

View File

@ -20,5 +20,9 @@ let project = Project(name: "MainApp",
sources: "Tests/**",
dependencies: [
.target(name: "App"),
]),
])
])],
additionalFiles: [
"Dangerfile.swift",
"Documentation/**",
.folderReference(path: "Responses"),
])

View File

@ -0,0 +1,5 @@
{
"id": "a",
"description": "Item a"
}

View File

@ -0,0 +1,7 @@
{
"items": [
"a",
"b",
"c"
]
}

View File

@ -20,5 +20,5 @@ let project = Project(name: "Framework1",
sources: "Tests/**",
dependencies: [
.target(name: "Framework1"),
]),
])
])
])