Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
### Fixed
- Fix `syncedFolder` source paths being relative to the spec directory instead of the project directory when they differ, which caused Xcode to treat the synced folder as empty #1636 @Ckitakishi
- Fix nested target attributes (e.g. `attributes.SystemCapabilities`) being serialized as a stringified Swift `Dictionary` description instead of a proper nested plist dictionary, which also caused non-deterministic key ordering in generated `project.pbxproj` files across runs #1639 @imadaan @sergeyospanov
- Fix `.DS_Store` and `.orig` files causing projects to be regenerated when using `--use-cache` #1622 @kimjune01

### Internal
- Use a dedicated local package in the SPM fixture so generated fixtures don't depend on the checkout directory name, such as when running tests from a git worktree @yonaskolb
Expand Down
20 changes: 20 additions & 0 deletions Sources/ProjectSpec/FileType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import JSONUtilities
import PathKit
import enum XcodeProj.BuildPhase

public struct FileType: Equatable {
Expand Down Expand Up @@ -124,3 +125,22 @@ extension FileType {
"xctestplan": FileType(buildPhase: BuildPhaseSpec.none),
]
}

extension FileType {

/// File names that are never added to the project or tracked by the cache
static let defaultExcludedFiles: Set<String> = [
".DS_Store",
]

/// File extensions that are never added to the project or tracked by the cache
static let defaultExcludedExtensions: Set<String> = [
"orig",
]

/// Checks whether the path is in any default excludes
public static func isDefaultExcluded(_ path: Path) -> Bool {
defaultExcludedFiles.contains(path.lastComponent)
|| (path.extension.map(defaultExcludedExtensions.contains) ?? false)
}
}
13 changes: 8 additions & 5 deletions Sources/ProjectSpec/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,16 @@ extension Project: PathContainer {
extension Project {

public var allTrackedFiles: [Path] {
// Skip files that SourceGenerator never adds to the project. Directories are kept, as they can still become groups
func trackedChildren(of path: Path) -> [Path] {
((try? path.recursiveChildren()) ?? []).filter { $0.isDirectory || !FileType.isDefaultExcluded($0) }
}

var files: [Path] = []
files.append(contentsOf: configFilePaths)
for fileGroup in fileGroups {
let fileGroupPath = basePath + fileGroup
let fileGroupChildren = (try? fileGroupPath.recursiveChildren()) ?? []
files.append(contentsOf: fileGroupChildren)
files.append(contentsOf: trackedChildren(of: fileGroupPath))
files.append(fileGroupPath)
}

Expand All @@ -289,11 +293,10 @@ extension Project {
files.append(contentsOf: target.configFilePaths)
for source in target.sources {
let sourcePath = basePath + source.path

let type = source.type ?? options.defaultSourceDirectoryType ?? .group
if type.projectTracksChildren {
let sourceChildren = (try? sourcePath.recursiveChildren()) ?? []
files.append(contentsOf: sourceChildren)
files.append(contentsOf: trackedChildren(of: sourcePath))
}
files.append(sourcePath)
}
Expand Down
10 changes: 1 addition & 9 deletions Sources/XcodeGenKit/SourceGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ class SourceGenerator {
private let project: Project
let pbxProj: PBXProj

private var defaultExcludedFiles = [
".DS_Store",
]
private let defaultExcludedExtensions = [
"orig",
]

private(set) var knownRegions: Set<String> = []

/// The effective base path for resolving group and file paths in the generated project.
Expand Down Expand Up @@ -471,8 +464,7 @@ class SourceGenerator {

/// Checks whether the path is not in any default or TargetSource excludes
func isIncludedPath(_ path: Path, excludePaths: Set<Path>, includePaths: SortedArray<Path>?) -> Bool {
return !defaultExcludedFiles.contains(where: { path.lastComponent == $0 })
&& !(path.extension.map(defaultExcludedExtensions.contains) ?? false)
return !FileType.isDefaultExcluded(path)
&& !excludePaths.contains(path)
// If includes is empty, it's included. If it's not empty, the path either needs to match exactly, or it needs to be a direct parent of an included path.
&& (includePaths.flatMap { _isIncludedPathSorted(path, sortedPaths: $0) } ?? true)
Expand Down
78 changes: 78 additions & 0 deletions Tests/ProjectSpecTests/ProjectSpecTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,84 @@ class ProjectSpecTests: XCTestCase {
}
}

func testAllTrackedFilesExcludesIgnoredFiles() {
describe {
let directoryPath = Path(components: [NSTemporaryDirectory(), ProcessInfo.processInfo.globallyUniqueString])

$0.after {
try? directoryPath.delete()
}

$0.it("excludes .DS_Store and .orig files from tracked source files") {
let sourcesDir = directoryPath + "Sources"
let featureDir = sourcesDir + "Feature"
let assetsDir = sourcesDir + "Assets.xcassets"
try featureDir.mkpath()
try assetsDir.mkpath()
try (sourcesDir + "main.swift").write("")
try (sourcesDir + ".DS_Store").write("")
try (sourcesDir + "file.swift.orig").write("")
try (featureDir + "Feature.swift").write("")
try (featureDir + ".DS_Store").write("")
try (assetsDir + ".DS_Store").write("")

let target = Target(
name: "App",
type: .application,
platform: .iOS,
sources: [TargetSource(path: "Sources")]
)
let project = Project(basePath: directoryPath, name: "Test", targets: [target])

try expect(Set(project.allTrackedFiles)) == [
sourcesDir,
sourcesDir + "main.swift",
featureDir,
featureDir + "Feature.swift",
assetsDir,
]
}

$0.it("excludes .DS_Store and .orig files from tracked fileGroup files") {
let fileGroupDir = directoryPath + "Resources"
try fileGroupDir.mkpath()
try (fileGroupDir + "image.png").write("")
try (fileGroupDir + ".DS_Store").write("")
try (fileGroupDir + "image.png.orig").write("")

let project = Project(
basePath: directoryPath,
name: "Test",
fileGroups: ["Resources"]
)

try expect(Set(project.allTrackedFiles)) == [
fileGroupDir,
fileGroupDir + "image.png",
]
}
}
}

func testDefaultExcludedFiles() {
describe {
$0.it("matches default excluded file names and extensions") {
try expect(FileType.isDefaultExcluded(".DS_Store")) == true
try expect(FileType.isDefaultExcluded("Sources/.DS_Store")) == true
try expect(FileType.isDefaultExcluded("Sources/file.swift.orig")) == true
}

$0.it("doesn't match similar names") {
try expect(FileType.isDefaultExcluded("Sources/file.swift")) == false
try expect(FileType.isDefaultExcluded("Sources/file.original")) == false
try expect(FileType.isDefaultExcluded("Sources/orig")) == false
try expect(FileType.isDefaultExcluded("Sources/.orig")) == false
try expect(FileType.isDefaultExcluded("Sources/file.ORIG")) == false
try expect(FileType.isDefaultExcluded("Sources/DS_Store")) == false
}
}
}

func testJSONEncodable() {
describe {
$0.it("encodes to json") {
Expand Down
Loading