Add Playground sources as playgrounds (#2132)
* Add Playground sources as playgrounds * Update CHANGELOG * Update documentation * Make regex more generic and revert Package.resolved changes * Address comments * Filter out the playgrounds from the resources too * Fix linting issues * Set the riht xcLanguageSpecificationIdentifier attribute to playgrounds
This commit is contained in:
parent
3dc9264b15
commit
ace1326f5c
|
@ -10,6 +10,11 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
|
||||
- Missing required module 'XXX' when building project with cached dependencies [#2051](https://github.com/tuist/tuist/pull/2051) by [@mollyIV](https://github.com/mollyIV).
|
||||
- Fix default generated scheme arguments [#2128](https://github.com/tuist/tuist/pull/2128) by [@kwridan](https://github.com/kwridan)
|
||||
- Playground files matched by the sources wildcards are added as playgrounds and not groups [#2132](https://github.com/tuist/tuist/pull/2132) by [@pepibumur](https://github.com/pepibumur).
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking** The implicit addition of playgrounds under `Playgrounds/` has been removed [#2132](https://github.com/tuist/tuist/pull/2132) by [@pepibumur](https://github.com/pepibumur).
|
||||
|
||||
## 1.27.0 - Hawái
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
public var launchArguments: [LaunchArgument]
|
||||
public var filesGroup: ProjectGroup
|
||||
public var scripts: [TargetScript]
|
||||
public var playgrounds: [AbsolutePath]
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
@ -68,7 +69,8 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
launchArguments: [LaunchArgument] = [],
|
||||
filesGroup: ProjectGroup,
|
||||
dependencies: [Dependency] = [],
|
||||
scripts: [TargetScript] = [])
|
||||
scripts: [TargetScript] = [],
|
||||
playgrounds: [AbsolutePath] = [])
|
||||
{
|
||||
self.name = name
|
||||
self.product = product
|
||||
|
@ -90,6 +92,7 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
self.filesGroup = filesGroup
|
||||
self.dependencies = dependencies
|
||||
self.scripts = scripts
|
||||
self.playgrounds = playgrounds
|
||||
}
|
||||
|
||||
/// Target can be included in the link phase of other targets
|
||||
|
|
|
@ -24,7 +24,8 @@ public extension Target {
|
|||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
dependencies: [Dependency] = [],
|
||||
scripts: [TargetScript] = [],
|
||||
launchArguments: [LaunchArgument] = []) -> Target
|
||||
launchArguments: [LaunchArgument] = [],
|
||||
playgrounds: [AbsolutePath] = []) -> Target
|
||||
{
|
||||
Target(name: name,
|
||||
platform: platform,
|
||||
|
@ -45,7 +46,8 @@ public extension Target {
|
|||
launchArguments: launchArguments,
|
||||
filesGroup: filesGroup,
|
||||
dependencies: dependencies,
|
||||
scripts: scripts)
|
||||
scripts: scripts,
|
||||
playgrounds: playgrounds)
|
||||
}
|
||||
|
||||
/// Creates a bare bones Target with as little data as possible
|
||||
|
|
|
@ -31,17 +31,10 @@ class ProjectFileElements {
|
|||
var sdks: [AbsolutePath: PBXFileReference] = [:]
|
||||
var knownRegions: Set<String> = Set([])
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private let playgrounds: Playgrounding
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(_ elements: [AbsolutePath: PBXFileElement] = [:],
|
||||
playgrounds: Playgrounding = Playgrounds())
|
||||
{
|
||||
init(_ elements: [AbsolutePath: PBXFileElement] = [:]) {
|
||||
self.elements = elements
|
||||
self.playgrounds = playgrounds
|
||||
}
|
||||
|
||||
func generateProjectFiles(project: Project,
|
||||
|
@ -67,12 +60,6 @@ class ProjectFileElements {
|
|||
pbxproj: pbxproj,
|
||||
sourceRootPath: project.sourceRootPath)
|
||||
|
||||
/// Playgrounds
|
||||
generatePlaygrounds(path: project.path,
|
||||
groups: groups,
|
||||
pbxproj: pbxproj,
|
||||
sourceRootPath: project.sourceRootPath)
|
||||
|
||||
// Products
|
||||
let directProducts = project.targets.map {
|
||||
GraphDependencyReference.product(target: $0.name, productName: $0.productNameWithExtension)
|
||||
|
@ -111,6 +98,7 @@ class ProjectFileElements {
|
|||
func targetFiles(target: Target) throws -> Set<GroupFileElement> {
|
||||
var files = Set<AbsolutePath>()
|
||||
files.formUnion(target.sources.map(\.path))
|
||||
files.formUnion(target.playgrounds)
|
||||
files.formUnion(target.coreDataModels.map(\.path))
|
||||
files.formUnion(target.coreDataModels.flatMap(\.versions))
|
||||
|
||||
|
@ -164,26 +152,6 @@ class ProjectFileElements {
|
|||
}
|
||||
}
|
||||
|
||||
func generatePlaygrounds(path: AbsolutePath,
|
||||
groups: ProjectGroups,
|
||||
pbxproj: PBXProj,
|
||||
sourceRootPath _: AbsolutePath)
|
||||
{
|
||||
let paths = playgrounds.paths(path: path)
|
||||
if paths.isEmpty { return }
|
||||
|
||||
let group = groups.playgrounds
|
||||
paths.forEach { playgroundPath in
|
||||
let name = playgroundPath.components.last!
|
||||
let reference = PBXFileReference(sourceTree: .group,
|
||||
lastKnownFileType: "file.playground",
|
||||
path: name,
|
||||
xcLanguageSpecificationIdentifier: "xcode.lang.swift")
|
||||
pbxproj.add(object: reference)
|
||||
group!.children.append(reference)
|
||||
}
|
||||
}
|
||||
|
||||
func generate(dependencyReferences: Set<GraphDependencyReference>,
|
||||
groups: ProjectGroups,
|
||||
pbxproj: PBXProj,
|
||||
|
@ -336,6 +304,15 @@ class ProjectFileElements {
|
|||
name: name,
|
||||
toGroup: toGroup,
|
||||
pbxproj: pbxproj)
|
||||
} else if isPlayground(path: absolutePath) {
|
||||
addPlayground(from: from,
|
||||
fileAbsolutePath: absolutePath,
|
||||
fileRelativePath: relativePath,
|
||||
name: name,
|
||||
toGroup: toGroup,
|
||||
pbxproj: pbxproj)
|
||||
return nil
|
||||
|
||||
} else {
|
||||
addFileElement(from: from,
|
||||
fileAbsolutePath: absolutePath,
|
||||
|
@ -454,6 +431,24 @@ class ProjectFileElements {
|
|||
elements[fileAbsolutePath] = file
|
||||
}
|
||||
|
||||
func addPlayground(from _: AbsolutePath,
|
||||
fileAbsolutePath: AbsolutePath,
|
||||
fileRelativePath: RelativePath,
|
||||
name: String?,
|
||||
toGroup: PBXGroup,
|
||||
pbxproj: PBXProj)
|
||||
{
|
||||
let lastKnownFileType = fileAbsolutePath.extension.flatMap { Xcode.filetype(extension: $0) }
|
||||
let file = PBXFileReference(sourceTree: .group,
|
||||
name: name,
|
||||
lastKnownFileType: lastKnownFileType,
|
||||
path: fileRelativePath.pathString,
|
||||
xcLanguageSpecificationIdentifier: "xcode.lang.swift")
|
||||
pbxproj.add(object: file)
|
||||
toGroup.children.append(file)
|
||||
elements[fileAbsolutePath] = file
|
||||
}
|
||||
|
||||
private func generateSDKFileElement(sdkNodePath: AbsolutePath,
|
||||
toGroup: PBXGroup,
|
||||
pbxproj: PBXProj)
|
||||
|
@ -501,6 +496,10 @@ class ProjectFileElements {
|
|||
path.extension == "lproj"
|
||||
}
|
||||
|
||||
func isPlayground(path: AbsolutePath) -> Bool {
|
||||
path.extension == "playground"
|
||||
}
|
||||
|
||||
func isVersionGroup(path: AbsolutePath) -> Bool {
|
||||
path.extension == "xcdatamodeld"
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ class ProjectGroups {
|
|||
@SortedPBXGroup var sortedMain: PBXGroup
|
||||
let products: PBXGroup
|
||||
let frameworks: PBXGroup
|
||||
let playgrounds: PBXGroup?
|
||||
|
||||
private let pbxproj: PBXProj
|
||||
private let projectGroups: [String: PBXGroup]
|
||||
|
@ -39,14 +38,12 @@ class ProjectGroups {
|
|||
projectGroups: [(name: String, group: PBXGroup)],
|
||||
products: PBXGroup,
|
||||
frameworks: PBXGroup,
|
||||
playgrounds: PBXGroup?,
|
||||
pbxproj: PBXProj)
|
||||
{
|
||||
sortedMain = main
|
||||
self.projectGroups = Dictionary(uniqueKeysWithValues: projectGroups)
|
||||
self.products = products
|
||||
self.frameworks = frameworks
|
||||
self.playgrounds = playgrounds
|
||||
self.pbxproj = pbxproj
|
||||
}
|
||||
|
||||
|
@ -66,8 +63,7 @@ class ProjectGroups {
|
|||
}
|
||||
|
||||
static func generate(project: Project,
|
||||
pbxproj: PBXProj,
|
||||
playgrounds: Playgrounding = Playgrounds()) -> ProjectGroups
|
||||
pbxproj: PBXProj) -> ProjectGroups
|
||||
{
|
||||
/// Main
|
||||
let projectRelativePath = project.sourceRootPath.relative(to: project.xcodeProjPath.parentDirectory).pathString
|
||||
|
@ -92,14 +88,6 @@ class ProjectGroups {
|
|||
pbxproj.add(object: frameworksGroup)
|
||||
mainGroup.children.append(frameworksGroup)
|
||||
|
||||
/// Playgrounds
|
||||
var playgroundsGroup: PBXGroup!
|
||||
if !playgrounds.paths(path: project.path).isEmpty {
|
||||
playgroundsGroup = PBXGroup(children: [], sourceTree: .group, path: "Playgrounds")
|
||||
pbxproj.add(object: playgroundsGroup)
|
||||
mainGroup.children.append(playgroundsGroup)
|
||||
}
|
||||
|
||||
/// Products
|
||||
let productsGroup = PBXGroup(children: [], sourceTree: .group, name: "Products")
|
||||
pbxproj.add(object: productsGroup)
|
||||
|
@ -109,7 +97,6 @@ class ProjectGroups {
|
|||
projectGroups: projectGroups,
|
||||
products: productsGroup,
|
||||
frameworks: frameworksGroup,
|
||||
playgrounds: playgroundsGroup,
|
||||
pbxproj: pbxproj)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// Protocol that defines an interface to interact with the project.
|
||||
protocol Playgrounding {
|
||||
/// Returns the list project Playgrounds in the given project directory.
|
||||
///
|
||||
/// - Parameter path: Directory where the project is defined.
|
||||
/// - Returns: List of paths.
|
||||
func paths(path: AbsolutePath) -> [AbsolutePath]
|
||||
}
|
||||
|
||||
final class Playgrounds: Playgrounding {
|
||||
/// Returns the list project Playgrounds in the given project directory.
|
||||
/// It enforces an implicit convention for the Playgrounds to be in the Playgrounds directory.
|
||||
///
|
||||
/// - Parameter path: Directory where the project is defined.
|
||||
/// - Returns: List of paths.
|
||||
func paths(path: AbsolutePath) -> [AbsolutePath] {
|
||||
path.glob("Playgrounds/*.playground").sorted()
|
||||
}
|
||||
}
|
|
@ -38,28 +38,10 @@ extension TuistCore.Target {
|
|||
let entitlements = try manifest.entitlements.map { try generatorPaths.resolve(path: $0) }
|
||||
|
||||
let settings = try manifest.settings.map { try TuistCore.Settings.from(manifest: $0, generatorPaths: generatorPaths) }
|
||||
let sources = try TuistCore.Target.sources(targetName: name, sources: manifest.sources?.globs.map { (glob: ProjectDescription.SourceFileGlob) in
|
||||
let globPath = try generatorPaths.resolve(path: glob.glob).pathString
|
||||
let excluding: [String] = try glob.excluding.compactMap { try generatorPaths.resolve(path: $0).pathString }
|
||||
return TuistCore.SourceFileGlob(glob: globPath, excluding: excluding, compilerFlags: glob.compilerFlags)
|
||||
} ?? [])
|
||||
|
||||
let resourceFilter = { (path: AbsolutePath) -> Bool in
|
||||
TuistCore.Target.isResource(path: path)
|
||||
}
|
||||
let (sources, sourcesPlaygrounds) = try sourcesAndPlaygrounds(manifest: manifest, targetName: name, generatorPaths: generatorPaths)
|
||||
|
||||
var invalidResourceGlobs: [InvalidGlob] = []
|
||||
|
||||
let resources: [TuistCore.FileElement] = try (manifest.resources ?? []).flatMap { manifest -> [TuistCore.FileElement] in
|
||||
do {
|
||||
return try TuistCore.FileElement.from(manifest: manifest,
|
||||
generatorPaths: generatorPaths,
|
||||
includeFiles: resourceFilter)
|
||||
} catch let GlobError.nonExistentDirectory(invalidGlob) {
|
||||
invalidResourceGlobs.append(invalidGlob)
|
||||
return []
|
||||
}
|
||||
}
|
||||
let (resources, resourcesPlaygrounds, invalidResourceGlobs) = try resourcesAndPlaygrounds(manifest: manifest, generatorPaths: generatorPaths)
|
||||
|
||||
if !invalidResourceGlobs.isEmpty {
|
||||
throw TargetManifestMapperError.invalidResourcesGlob(targetName: name, invalidGlobs: invalidResourceGlobs)
|
||||
|
@ -82,6 +64,8 @@ extension TuistCore.Target {
|
|||
let environment = manifest.environment
|
||||
let launchArguments = manifest.launchArguments.map(LaunchArgument.from)
|
||||
|
||||
let playgrounds = sourcesPlaygrounds + resourcesPlaygrounds
|
||||
|
||||
return TuistCore.Target(name: name,
|
||||
platform: platform,
|
||||
product: product,
|
||||
|
@ -100,6 +84,70 @@ extension TuistCore.Target {
|
|||
environment: environment,
|
||||
launchArguments: launchArguments,
|
||||
filesGroup: .group(name: "Project"),
|
||||
dependencies: dependencies)
|
||||
dependencies: dependencies,
|
||||
playgrounds: playgrounds)
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
||||
// swiftlint:disable line_length
|
||||
fileprivate static func resourcesAndPlaygrounds(manifest: ProjectDescription.Target,
|
||||
generatorPaths: GeneratorPaths) throws -> (resources: [TuistCore.FileElement], playgrounds: [AbsolutePath], invalidResourceGlobs: [InvalidGlob])
|
||||
{
|
||||
// swiftlint:enable line_length
|
||||
let resourceFilter = { (path: AbsolutePath) -> Bool in
|
||||
TuistCore.Target.isResource(path: path)
|
||||
}
|
||||
|
||||
var invalidResourceGlobs: [InvalidGlob] = []
|
||||
var resourcesWithoutPlaygrounds: [TuistCore.FileElement] = []
|
||||
var playgrounds: Set<AbsolutePath> = []
|
||||
|
||||
let allResources = try (manifest.resources ?? []).flatMap { manifest -> [TuistCore.FileElement] in
|
||||
do {
|
||||
return try TuistCore.FileElement.from(manifest: manifest,
|
||||
generatorPaths: generatorPaths,
|
||||
includeFiles: resourceFilter)
|
||||
} catch let GlobError.nonExistentDirectory(invalidGlob) {
|
||||
invalidResourceGlobs.append(invalidGlob)
|
||||
return []
|
||||
}
|
||||
}
|
||||
allResources.forEach { fileElement in
|
||||
switch fileElement {
|
||||
case .folderReference: resourcesWithoutPlaygrounds.append(fileElement)
|
||||
case let .file(path):
|
||||
if path.pathString.contains(".playground/") {
|
||||
playgrounds.insert(path.upToComponentMatching(extension: "playground"))
|
||||
} else {
|
||||
resourcesWithoutPlaygrounds.append(fileElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (resources: resourcesWithoutPlaygrounds, playgrounds: Array(playgrounds), invalidResourceGlobs: invalidResourceGlobs)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
fileprivate static func sourcesAndPlaygrounds(manifest: ProjectDescription.Target, targetName: String, generatorPaths: GeneratorPaths) throws -> (sources: [TuistCore.SourceFile], playgrounds: [AbsolutePath]) {
|
||||
var sourcesWithoutPlaygrounds: [TuistCore.SourceFile] = []
|
||||
var playgrounds: Set<AbsolutePath> = []
|
||||
|
||||
// Sources
|
||||
let allSources = try TuistCore.Target.sources(targetName: targetName, sources: manifest.sources?.globs.map { (glob: ProjectDescription.SourceFileGlob) in
|
||||
let globPath = try generatorPaths.resolve(path: glob.glob).pathString
|
||||
let excluding: [String] = try glob.excluding.compactMap { try generatorPaths.resolve(path: $0).pathString }
|
||||
return TuistCore.SourceFileGlob(glob: globPath, excluding: excluding, compilerFlags: glob.compilerFlags)
|
||||
} ?? [])
|
||||
|
||||
allSources.forEach { sourceFile in
|
||||
if sourceFile.path.pathString.contains(".playground/") {
|
||||
playgrounds.insert(sourceFile.path.upToComponentMatching(extension: "playground"))
|
||||
} else {
|
||||
sourcesWithoutPlaygrounds.append(sourceFile)
|
||||
}
|
||||
}
|
||||
|
||||
return (sources: sourcesWithoutPlaygrounds, playgrounds: Array(playgrounds))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,32 @@ extension AbsolutePath {
|
|||
return ancestorPath
|
||||
}
|
||||
|
||||
public func upToComponentMatching(regex: String) -> AbsolutePath {
|
||||
if isRoot { return self }
|
||||
if basename.range(of: regex, options: .regularExpression) == nil {
|
||||
return parentDirectory.upToComponentMatching(regex: regex)
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public func upToComponentMatching(extension: String) -> AbsolutePath {
|
||||
if isRoot { return self }
|
||||
if self.extension == `extension` {
|
||||
return self
|
||||
} else {
|
||||
return parentDirectory.upToComponentMatching(extension: `extension`)
|
||||
}
|
||||
}
|
||||
|
||||
public var upToLastNonGlob: AbsolutePath {
|
||||
guard let index = components.firstIndex(where: { $0.isGlobComponent }) else {
|
||||
return self
|
||||
}
|
||||
|
||||
return AbsolutePath(components[0 ..< index].joined(separator: "/"))
|
||||
}
|
||||
|
||||
/// Returns the hash of the file the path points to.
|
||||
public func sha256() -> Data? {
|
||||
try? SHA256Digest.file(at: url)
|
||||
|
@ -122,13 +148,3 @@ extension String {
|
|||
return rangeOfCharacter(from: globCharacters) != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension AbsolutePath {
|
||||
var upToLastNonGlob: AbsolutePath {
|
||||
guard let index = components.firstIndex(where: { $0.isGlobComponent }) else {
|
||||
return self
|
||||
}
|
||||
|
||||
return AbsolutePath(components[0 ..< index].joined(separator: "/"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -642,7 +642,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
func test_generateTarget_actions() throws {
|
||||
// Given
|
||||
system.swiftVersionStub = { "5.2" }
|
||||
let fileElements = ProjectFileElements([:], playgrounds: MockPlaygrounds())
|
||||
let fileElements = ProjectFileElements([:])
|
||||
let graph = Graph.test()
|
||||
let valueGraph = ValueGraph(graph: graph)
|
||||
let graphTraverser = ValueGraphTraverser(graph: valueGraph)
|
||||
|
@ -657,8 +657,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
])
|
||||
let project = Project.test(path: path, sourceRootPath: path, xcodeProjPath: path.appending(component: "Project.xcodeproj"), targets: [target])
|
||||
let groups = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: MockPlaygrounds())
|
||||
pbxproj: pbxproj)
|
||||
try fileElements.generateProjectFiles(project: project,
|
||||
graphTraverser: graphTraverser,
|
||||
groups: groups,
|
||||
|
|
|
@ -638,7 +638,7 @@ final class LinkGeneratorTests: XCTestCase {
|
|||
}
|
||||
|
||||
func createProjectFileElements(for targets: [Target]) -> ProjectFileElements {
|
||||
let projectFileElements = ProjectFileElements(playgrounds: MockPlaygrounds())
|
||||
let projectFileElements = ProjectFileElements()
|
||||
targets.forEach {
|
||||
projectFileElements.products[$0.name] = PBXFileReference(path: $0.productNameWithExtension)
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
@testable import TuistGenerator
|
||||
|
||||
final class MockPlaygrounds: Playgrounding {
|
||||
var pathsStub: ((AbsolutePath) -> [AbsolutePath])?
|
||||
|
||||
func paths(path: AbsolutePath) -> [AbsolutePath] {
|
||||
pathsStub?(path) ?? []
|
||||
}
|
||||
}
|
|
@ -9,23 +9,19 @@ import XCTest
|
|||
|
||||
final class ProjectFileElementsTests: TuistUnitTestCase {
|
||||
var subject: ProjectFileElements!
|
||||
var playgrounds: MockPlaygrounds!
|
||||
var groups: ProjectGroups!
|
||||
var pbxproj: PBXProj!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
playgrounds = MockPlaygrounds()
|
||||
pbxproj = PBXProj()
|
||||
groups = ProjectGroups.generate(project: .test(path: "/path", sourceRootPath: "/path", xcodeProjPath: "/path/Project.xcodeproj"),
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: MockPlaygrounds())
|
||||
pbxproj: pbxproj)
|
||||
|
||||
subject = ProjectFileElements(playgrounds: playgrounds)
|
||||
subject = ProjectFileElements()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
playgrounds = nil
|
||||
pbxproj = nil
|
||||
groups = nil
|
||||
subject = nil
|
||||
|
@ -311,7 +307,8 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
headers: Headers(public: [AbsolutePath("/project/public.h")],
|
||||
private: [AbsolutePath("/project/private.h")],
|
||||
project: [AbsolutePath("/project/project.h")]),
|
||||
dependencies: [])
|
||||
dependencies: [],
|
||||
playgrounds: ["/project/MyPlayground.playground"])
|
||||
|
||||
// When
|
||||
let files = try subject.targetFiles(target: target)
|
||||
|
@ -321,6 +318,7 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
GroupFileElement(path: "/project/debug.xcconfig", group: target.filesGroup),
|
||||
GroupFileElement(path: "/project/release.xcconfig", group: target.filesGroup),
|
||||
GroupFileElement(path: "/project/file.swift", group: target.filesGroup),
|
||||
GroupFileElement(path: "/project/MyPlayground.playground", group: target.filesGroup),
|
||||
GroupFileElement(path: "/project/image.png", group: target.filesGroup),
|
||||
GroupFileElement(path: "/project/reference", group: target.filesGroup, isReference: true),
|
||||
GroupFileElement(path: "/project/public.h", group: target.filesGroup),
|
||||
|
@ -512,6 +510,31 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
])
|
||||
}
|
||||
|
||||
func test_addPlayground() throws {
|
||||
// Given
|
||||
let from = AbsolutePath("/project/")
|
||||
let fileAbsolutePath = AbsolutePath("/project/MyPlayground.playground")
|
||||
let fileRelativePath = RelativePath("./MyPlayground.playground")
|
||||
let group = PBXGroup()
|
||||
let pbxproj = PBXProj()
|
||||
pbxproj.add(object: group)
|
||||
|
||||
// When
|
||||
subject.addPlayground(from: from,
|
||||
fileAbsolutePath: fileAbsolutePath,
|
||||
fileRelativePath: fileRelativePath,
|
||||
name: nil,
|
||||
toGroup: group,
|
||||
pbxproj: pbxproj)
|
||||
|
||||
// Then
|
||||
let file: PBXFileReference? = group.children.first as? PBXFileReference
|
||||
XCTAssertEqual(file?.path, "MyPlayground.playground")
|
||||
XCTAssertEqual(file?.sourceTree, .group)
|
||||
XCTAssertNil(file?.name)
|
||||
XCTAssertEqual(file?.lastKnownFileType, Xcode.filetype(extension: fileAbsolutePath.extension!))
|
||||
}
|
||||
|
||||
func test_addVersionGroupElement() throws {
|
||||
let from = AbsolutePath("/project/")
|
||||
let folderAbsolutePath = AbsolutePath("/project/model.xcdatamodel")
|
||||
|
@ -552,39 +575,6 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
XCTAssertEqual(file?.lastKnownFileType, Xcode.filetype(extension: "swift"))
|
||||
}
|
||||
|
||||
func test_generatePlaygrounds() throws {
|
||||
let pbxproj = PBXProj()
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
|
||||
let playgroundsPath = temporaryPath.appending(component: "Playgrounds")
|
||||
let playgroundPath = playgroundsPath.appending(component: "Test.playground")
|
||||
|
||||
playgrounds.pathsStub = { projectPath in
|
||||
if projectPath == temporaryPath {
|
||||
return [playgroundPath]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
let project = Project.test(path: temporaryPath,
|
||||
sourceRootPath: temporaryPath,
|
||||
xcodeProjPath: temporaryPath.appending(component: "Project.xcodeproj"))
|
||||
let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, playgrounds: playgrounds)
|
||||
|
||||
subject.generatePlaygrounds(path: temporaryPath,
|
||||
groups: groups,
|
||||
pbxproj: pbxproj,
|
||||
sourceRootPath: temporaryPath)
|
||||
let file: PBXFileReference? = groups.playgrounds?.children.first as? PBXFileReference
|
||||
|
||||
XCTAssertEqual(file?.sourceTree, .group)
|
||||
XCTAssertEqual(file?.lastKnownFileType, "file.playground")
|
||||
XCTAssertEqual(file?.path, "Test.playground")
|
||||
XCTAssertNil(file?.name)
|
||||
XCTAssertEqual(file?.xcLanguageSpecificationIdentifier, "xcode.lang.swift")
|
||||
}
|
||||
|
||||
func test_group() {
|
||||
let group = PBXGroup()
|
||||
let path = AbsolutePath("/path/to/folder")
|
||||
|
@ -604,6 +594,11 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
XCTAssertTrue(subject.isLocalized(path: path))
|
||||
}
|
||||
|
||||
func test_isPlayground() {
|
||||
let path = AbsolutePath("/path/to/MyPlayground.playground")
|
||||
XCTAssertTrue(subject.isPlayground(path: path))
|
||||
}
|
||||
|
||||
func test_isVersionGroup() {
|
||||
let path = AbsolutePath("/path/to/model.xcdatamodeld")
|
||||
XCTAssertTrue(subject.isVersionGroup(path: path))
|
||||
|
|
|
@ -10,7 +10,6 @@ import XCTest
|
|||
|
||||
final class ProjectGroupsTests: XCTestCase {
|
||||
var subject: ProjectGroups!
|
||||
var playgrounds: MockPlaygrounds!
|
||||
var sourceRootPath: AbsolutePath!
|
||||
var project: Project!
|
||||
var pbxproj: PBXProj!
|
||||
|
@ -19,7 +18,6 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
super.setUp()
|
||||
|
||||
let path = AbsolutePath("/test/")
|
||||
playgrounds = MockPlaygrounds()
|
||||
sourceRootPath = AbsolutePath("/test/")
|
||||
project = Project(path: path,
|
||||
sourceRootPath: path,
|
||||
|
@ -45,26 +43,17 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
pbxproj = nil
|
||||
project = nil
|
||||
sourceRootPath = nil
|
||||
playgrounds = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_generate() {
|
||||
playgrounds.pathsStub = { projectPath in
|
||||
if projectPath == self.sourceRootPath {
|
||||
return [self.sourceRootPath.appending(RelativePath("Playgrounds/Test.playground"))]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
pbxproj: pbxproj)
|
||||
|
||||
let main = subject.sortedMain
|
||||
XCTAssertNil(main.path)
|
||||
XCTAssertEqual(main.sourceTree, .group)
|
||||
XCTAssertEqual(main.children.count, 5)
|
||||
XCTAssertEqual(main.children.count, 4)
|
||||
|
||||
XCTAssertNotNil(main.group(named: "Project"))
|
||||
XCTAssertNil(main.group(named: "Project")?.path)
|
||||
|
@ -83,31 +72,10 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
XCTAssertEqual(subject.products.name, "Products")
|
||||
XCTAssertNil(subject.products.path)
|
||||
XCTAssertEqual(subject.products.sourceTree, .group)
|
||||
|
||||
XCTAssertNotNil(subject.playgrounds)
|
||||
XCTAssertTrue(main.children.contains(subject.playgrounds!))
|
||||
XCTAssertEqual(subject.playgrounds?.path, "Playgrounds")
|
||||
XCTAssertNil(subject.playgrounds?.name)
|
||||
XCTAssertEqual(subject.playgrounds?.sourceTree, .group)
|
||||
}
|
||||
|
||||
func test_generate_when_there_are_no_playgrounds() {
|
||||
playgrounds.pathsStub = { _ in
|
||||
[]
|
||||
}
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
|
||||
XCTAssertNil(subject.playgrounds)
|
||||
}
|
||||
|
||||
func test_generate_groupsOrder() throws {
|
||||
// Given
|
||||
playgrounds.pathsStub = { _ in
|
||||
[AbsolutePath("/Playgrounds/Test.playground")]
|
||||
}
|
||||
|
||||
let target1 = Target.test(filesGroup: .group(name: "B"))
|
||||
let target2 = Target.test(filesGroup: .group(name: "C"))
|
||||
let target3 = Target.test(filesGroup: .group(name: "A"))
|
||||
|
@ -117,8 +85,7 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
|
||||
// When
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
pbxproj: pbxproj)
|
||||
|
||||
// Then
|
||||
// swiftformat:disable preferKeyPath
|
||||
|
@ -130,15 +97,13 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
"C",
|
||||
"A",
|
||||
"Frameworks",
|
||||
"Playgrounds",
|
||||
"Products",
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetFrameworks() throws {
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
pbxproj: pbxproj)
|
||||
|
||||
let got = try subject.targetFrameworks(target: "Test")
|
||||
XCTAssertEqual(got.name, "Test")
|
||||
|
@ -149,8 +114,7 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
func test_projectGroup_unknownProjectGroups() throws {
|
||||
// Given
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
pbxproj: pbxproj)
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(try subject.projectGroup(named: "abc"),
|
||||
|
@ -169,8 +133,7 @@ final class ProjectGroupsTests: XCTestCase {
|
|||
targets: [target1, target2, target3])
|
||||
|
||||
subject = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: playgrounds)
|
||||
pbxproj: pbxproj)
|
||||
|
||||
// When / Then
|
||||
XCTAssertNotNil(try? subject.projectGroup(named: "A"))
|
||||
|
|
|
@ -19,7 +19,7 @@ final class TargetGeneratorTests: XCTestCase {
|
|||
path = AbsolutePath("/test")
|
||||
pbxproj = PBXProj()
|
||||
pbxProject = createPbxProject(pbxproj: pbxproj)
|
||||
fileElements = ProjectFileElements([:], playgrounds: MockPlaygrounds())
|
||||
fileElements = ProjectFileElements([:])
|
||||
|
||||
subject = TargetGenerator()
|
||||
}
|
||||
|
@ -59,8 +59,7 @@ final class TargetGeneratorTests: XCTestCase {
|
|||
let valueGraph = ValueGraph(graph: graph)
|
||||
let graphTraverser = ValueGraphTraverser(graph: valueGraph)
|
||||
let groups = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: MockPlaygrounds())
|
||||
pbxproj: pbxproj)
|
||||
try fileElements.generateProjectFiles(project: project,
|
||||
graphTraverser: graphTraverser,
|
||||
groups: groups,
|
||||
|
@ -138,8 +137,7 @@ final class TargetGeneratorTests: XCTestCase {
|
|||
])
|
||||
let project = Project.test(path: path, sourceRootPath: path, xcodeProjPath: path.appending(component: "Project.xcodeproj"), targets: [target])
|
||||
let groups = ProjectGroups.generate(project: project,
|
||||
pbxproj: pbxproj,
|
||||
playgrounds: MockPlaygrounds())
|
||||
pbxproj: pbxproj)
|
||||
try fileElements.generateProjectFiles(project: project,
|
||||
graphTraverser: graphTraverser,
|
||||
groups: groups,
|
||||
|
|
|
@ -62,4 +62,26 @@ final class AbsolutePathExtrasTests: TuistUnitTestCase {
|
|||
// Then
|
||||
XCTAssertThrowsSpecific(try dir.throwingGlob("invalid/path/**/*"), GlobError.nonExistentDirectory(InvalidGlob(pattern: dir.appending(RelativePath("invalid/path/**/*")).pathString, nonExistentPath: dir.appending(RelativePath("invalid/path/")))))
|
||||
}
|
||||
|
||||
func test_upToComponentMatchingRegex() throws {
|
||||
// Given
|
||||
let path = AbsolutePath("/path/to/sources/Playground.playground/Content.swift")
|
||||
|
||||
// When
|
||||
let got = path.upToComponentMatching(regex: ".+\\.playground")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "/path/to/sources/Playground.playground")
|
||||
}
|
||||
|
||||
func test_upToComponentMatchingExtension() throws {
|
||||
// Given
|
||||
let path = AbsolutePath("/path/to/sources/Playground.playground/Content.swift")
|
||||
|
||||
// When
|
||||
let got = path.upToComponentMatching(extension: "playground")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "/path/to/sources/Playground.playground")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,7 +263,8 @@ Each target in the list of project targets can be initialized with the following
|
|||
},
|
||||
{
|
||||
name: 'Sources',
|
||||
description: 'Source files that are compiled by the target',
|
||||
description:
|
||||
'Source files that are compiled by the target. Any playgrounds matched by the globs used in this property will be automatically added.',
|
||||
type: 'SourceFilesList',
|
||||
typeLink: '#source-file-list',
|
||||
optional: false,
|
||||
|
|
Loading…
Reference in New Issue