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:
Pedro Piñera Buendía 2020-12-16 14:54:08 +01:00 committed by GitHub
parent 3dc9264b15
commit ace1326f5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 213 additions and 209 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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))
}
}

View File

@ -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: "/"))
}
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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) ?? []
}
}

View File

@ -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))

View File

@ -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"))

View File

@ -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,

View File

@ -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")
}
}

View File

@ -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,