Skip to content

Commit

Permalink
Adds FilesLinter that makes sure all required files are available
Browse files Browse the repository at this point in the history
Creates a file linter that check if all non-glob Files within the
targets are available within the generated targets and project

Moves File validation into the TargetLinter as discussed
  • Loading branch information
mustiikhalil committed May 3, 2024
1 parent d77b13e commit b21df9f
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 60 deletions.
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 @@ -41,6 +42,7 @@ class TargetLinter: TargetLinting {
issues.append(contentsOf: validateCoreDataModelsExist(target: target))
issues.append(contentsOf: validateCoreDataModelVersionsExist(target: target))
issues.append(contentsOf: lintMergeableLibrariesOnlyAppliesToDynamicTargets(target: target))
issues.append(contentsOf: lintAllRequiredFilesAvailable(target: target))
for script in target.scripts {
issues.append(contentsOf: targetScriptLinter.lint(script))
}
Expand Down Expand Up @@ -323,6 +325,35 @@ class TargetLinter: TargetLinting {
}
return []
}

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

0 comments on commit b21df9f

Please sign in to comment.