Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds FilesLinter that makes sure all required files are available #6069

Closed
wants to merge 1 commit into from
Closed
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
20 changes: 13 additions & 7 deletions Sources/TuistCore/Graph/ModelExtensions/Target+Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,19 @@ extension Target {

let paths: [AbsolutePath]

do {
paths = try FileHandler.shared
.throwingGlob(base, glob: sourcePath.basename)
.filter { !$0.isInOpaqueDirectory }
} catch let GlobError.nonExistentDirectory(invalidGlob) {
paths = []
invalidGlobs.append(invalidGlob)
if source.path.pathString.isGlobComponent {
do {
paths = try FileHandler.shared
.throwingGlob(base, glob: sourcePath.basename)
.filter {
return !$0.isInOpaqueDirectory
}
} catch let GlobError.nonExistentDirectory(invalidGlob) {
paths = []
invalidGlobs.append(invalidGlob)
}
} else {
paths = [source.path]
}

Set(paths)
Expand Down
31 changes: 31 additions & 0 deletions Sources/TuistGenerator/Linter/TargetLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
import TuistCore
import TuistGraph
import TuistSupport
import TSCBasic

protocol TargetLinting: AnyObject {
func lint(target: Target) -> [LintingIssue]
Expand Down Expand Up @@ -42,6 +43,7 @@ class TargetLinter: TargetLinting {
issues.append(contentsOf: validateCoreDataModelVersionsExist(target: target))
issues.append(contentsOf: lintMergeableLibrariesOnlyAppliesToDynamicTargets(target: target))
issues.append(contentsOf: lintOnDemandResourcesTags(target: target))
issues.append(contentsOf: lintAllRequiredFilesAvailable(target: target))
for script in target.scripts {
issues.append(contentsOf: targetScriptLinter.lint(script))
}
Expand Down Expand Up @@ -337,6 +339,35 @@ class TargetLinter: TargetLinting {
)
}
}

private func lintAllRequiredFilesAvailable(target: Target) -> [LintingIssue] {
var lintingIssues: [LintingIssue] = []

lintingIssues.append(contentsOf: lintFiles(paths: target.sources.map { $0.path }))

if target.supportsResources {
lintingIssues.append(contentsOf: lintFiles(paths: target.resources.resources.map { $0.path }))
lintingIssues.append(contentsOf: lintFiles(paths: target.copyFiles.flatMap(\.files).map { $0.path }))
}

return lintingIssues
}

private func lintFiles(paths: [AbsolutePath]) -> [LintingIssue] {
var lintingIssues: [LintingIssue] = []

for path in paths {
if !FileHandler.shared.exists(path) {
lintingIssues.append(LintingIssue(reason: "No files found at: \(path)", severity: .warning))
continue
}

if FileHandler.shared.isFolder(path) && !FileHandler.shared.exists(path) {
lintingIssues.append(LintingIssue(reason: "No folder found at: \(path)", severity: .warning))
}
}
return lintingIssues
}
}

extension TargetDependency {
Expand Down
9 changes: 7 additions & 2 deletions Sources/TuistGraph/Models/SourceFileGlob.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import Foundation
import TSCBasic

/// A type that represents a list of source files defined by a glob.
public struct SourceFileGlob: Equatable {
/// Glob pattern to unfold all the source files.
public let glob: String

/// Reference to the AbsolutePath
public let path: AbsolutePath

/// Glob pattern used for filtering out files.
public let excluding: [String]

Expand All @@ -25,13 +29,14 @@ public struct SourceFileGlob: Equatable {
/// - codeGen: Source file code generation attribute.
/// - compilationCondition: Condition for file compilation.
public init(
glob: String,
glob: AbsolutePath,
excluding: [String] = [],
compilerFlags: String? = nil,
codeGen: FileCodeGen? = nil,
compilationCondition: PlatformCondition? = nil
) {
self.glob = glob
self.glob = glob.pathString
path = glob
self.excluding = excluding
self.compilerFlags = compilerFlags
self.codeGen = codeGen
Expand Down
2 changes: 1 addition & 1 deletion Sources/TuistGraph/Models/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
"playground", "rcproject", "mlpackage",
]
public static let validFolderExtensions: [String] = [
"framework", "bundle", "app", "xcassets", "appiconset", "scnassets",
"framework", "bundle", "app", "xcassets", "appiconset", "scnassets", "strings",
]

// MARK: - Attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,27 @@ extension TuistGraph.CopyFileElement {
func globFiles(_ path: AbsolutePath) throws -> [AbsolutePath] {
if FileHandler.shared.exists(path), !FileHandler.shared.isFolder(path) { return [path] }

if !path.pathString.isGlobComponent {
return [path]
}

let files = try FileHandler.shared.throwingGlob(AbsolutePath.root, glob: String(path.pathString.dropFirst()))
.filter(includeFiles)

if files.isEmpty {
if FileHandler.shared.isFolder(path) {
logger.warning("'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
} else {
// FIXME: This should be done in a linter.
logger.warning("No files found at: \(path.pathString)")
}
if files.isEmpty && FileHandler.shared.isFolder(path) {
logger.warning("'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
}

return files
}

func folderReferences(_ path: AbsolutePath) -> [AbsolutePath] {
guard FileHandler.shared.exists(path) else {
// FIXME: This should be done in a linter.
logger.warning("\(path.pathString) does not exist")
return []
}

guard FileHandler.shared.isFolder(path) else {
// FIXME: This should be done in a linter.
logger.warning("\(path.pathString) is not a directory - folder reference paths need to point to directories")
return []
}

return [path]
}

switch manifest {
case let .glob(pattern: pattern, condition: condition):
let resolvedPath = try generatorPaths.resolve(path: pattern)
return try globFiles(resolvedPath).map { .file(path: $0, condition: condition?.asGraphCondition) }
case let .folderReference(path: folderReferencePath, condition: condition):
let resolvedPath = try generatorPaths.resolve(path: folderReferencePath)
return folderReferences(resolvedPath).map { .folderReference(path: $0, condition: condition?.asGraphCondition) }
return [resolvedPath].map { .folderReference(path: $0, condition: condition?.asGraphCondition) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,23 @@ extension TuistGraph.ResourceFileElement {
excluded.formUnion(globs)
}

if !path.pathString.isGlobComponent {
return [path]
}

let files = try FileHandler.shared
.throwingGlob(.root, glob: String(path.pathString.dropFirst()))
.filter { !$0.isInOpaqueDirectory }
.filter(includeFiles)
.filter { !excluded.contains($0) }

if files.isEmpty {
if FileHandler.shared.isFolder(path) {
logger.warning("'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
} else {
// FIXME: This should be done in a linter.
logger.warning("No files found at: \(path.pathString)")
}
if files.isEmpty && FileHandler.shared.isFolder(path) {
logger.warning("'\(path.pathString)' is a directory, try using: '\(path.pathString)/**' to list its files")
}

return files
}

func folderReferences(_ path: AbsolutePath) -> [AbsolutePath] {
guard FileHandler.shared.exists(path) else {
// FIXME: This should be done in a linter.
logger.warning("\(path.pathString) does not exist")
return []
}

guard FileHandler.shared.isFolder(path) else {
// FIXME: This should be done in a linter.
logger.warning("\(path.pathString) is not a directory - folder reference paths need to point to directories")
return []
}

return [path]
}

switch manifest {
case let .glob(pattern, excluding, tags, condition):
let resolvedPath = try generatorPaths.resolve(path: pattern)
Expand All @@ -69,7 +52,7 @@ extension TuistGraph.ResourceFileElement {
) }
case let .folderReference(folderReferencePath, tags, condition):
let resolvedPath = try generatorPaths.resolve(path: folderReferencePath)
return folderReferences(resolvedPath).map { ResourceFileElement.folderReference(
return [resolvedPath].map { ResourceFileElement.folderReference(
path: $0,
tags: tags,
inclusionCondition: condition?.asGraphCondition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ extension TuistGraph.Target {

// Sources
let allSources = try TuistGraph.Target.sources(targetName: targetName, sources: manifest.sources?.globs.map { glob in
let globPath = try generatorPaths.resolve(path: glob.glob).pathString
let globPath = try generatorPaths.resolve(path: glob.glob)
let excluding: [String] = try glob.excluding.compactMap { try generatorPaths.resolve(path: $0).pathString }
let mappedCodeGen = glob.codeGen.map(TuistGraph.FileCodeGen.from)
return TuistGraph.SourceFileGlob(
Expand Down
2 changes: 1 addition & 1 deletion Sources/TuistSupport/Extensions/AbsolutePath+Extras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ extension AbsolutePath: ExpressibleByStringLiteral {
}

extension String {
var isGlobComponent: Bool {
public var isGlobComponent: Bool {
let globCharacters = CharacterSet(charactersIn: "*{}")
return rangeOfCharacter(from: globCharacters) != nil
}
Expand Down