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 Apr 11, 2024
1 parent 5271cb3 commit c9547a7
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 53 deletions.
9 changes: 7 additions & 2 deletions Sources/TuistCore/Graph/ModelExtensions/Target+Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,16 @@ extension Target {
/// This method unfolds the source file globs subtracting the paths that are excluded and ignoring
/// the files that don't have a supported source extension.
/// - Parameter sources: List of source file glob to be unfolded.
public static func sources(targetName: String, sources: [SourceFileGlob]) throws -> [TuistGraph.SourceFile] {
public static func sources(targetName: String, sources: [SourceFileGlob]) throws -> (sourceFiles: [TuistGraph.SourceFile], paths: [AbsolutePath]) {
var sourceFiles: [AbsolutePath: TuistGraph.SourceFile] = [:]
var invalidGlobs: [InvalidGlob] = []
var noneGlobPaths: [AbsolutePath] = []

for source in sources {
if !source.glob.isGlobComponent {
noneGlobPaths.append(source.path)
}

let sourcePath = try AbsolutePath(validating: source.glob)
let base = try AbsolutePath(validating: sourcePath.dirname)

Expand Down Expand Up @@ -98,6 +103,6 @@ extension Target {
throw TargetError.invalidSourcesGlob(targetName: targetName, invalidGlobs: invalidGlobs)
}

return Array(sourceFiles.values)
return (Array(sourceFiles.values), noneGlobPaths)
}
}
46 changes: 46 additions & 0 deletions Sources/TuistGenerator/Linter/TargetLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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 @@ -292,6 +293,51 @@ class TargetLinter: TargetLinting {
}
return []
}

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

for path in target.sourcesPaths {
if target.sources.isEmpty && !path.isOpaqueDirectory {
lintingIssues.append(LintingIssue(reason: "No files found at: \(path)", severity: .warning))
continue
}
guard target.sources.first(where: { $0.path == path }) != nil else {
lintingIssues.append(LintingIssue(reason: "No files found at: \(path)", severity: .warning))
continue
}
}

if target.supportsResources {
for path in target.resourcesPaths {
if target.resources.resources.isEmpty {
lintingIssues.append(LintingIssue(reason: "No resources found at: \(path)", severity: .warning))
continue
}
guard target.resources.resources.first(where: { $0.path == path }) != nil else {
lintingIssues.append(LintingIssue(reason: "No resources found at: \(path)", severity: .warning))
continue
}
}
}

if target.supportsResources {
let files = target.copyFiles.flatMap { $0.files }

for path in target.copyFilesPaths {
if target.copyFiles.isEmpty {
lintingIssues.append(LintingIssue(reason: "No copy files found at: \(path)", severity: .warning))
continue
}
guard files.first(where: { $0.path == path }) != nil else {
lintingIssues.append(LintingIssue(reason: "No copy files found at: \(path)", severity: .warning))
continue
}
}
}

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
self.path = glob
self.excluding = excluding
self.compilerFlags = compilerFlags
self.codeGen = codeGen
Expand Down
11 changes: 10 additions & 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 All @@ -31,8 +31,11 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
public var settings: Settings?
public var dependencies: [TargetDependency]
public var sources: [SourceFile]
public var sourcesPaths: [AbsolutePath]
public var resources: ResourceFileElements
public var resourcesPaths: [AbsolutePath]
public var copyFiles: [CopyFilesAction]
public var copyFilesPaths: [AbsolutePath]
public var headers: Headers?
public var coreDataModels: [CoreDataModel]
public var scripts: [TargetScript]
Expand Down Expand Up @@ -60,8 +63,11 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
entitlements: Entitlements? = nil,
settings: Settings? = nil,
sources: [SourceFile] = [],
sourcesPaths: [AbsolutePath] = [],
resources: ResourceFileElements = .init([]),
resourcesPaths: [AbsolutePath] = [],
copyFiles: [CopyFilesAction] = [],
copyFilesPaths: [AbsolutePath] = [],
headers: Headers? = nil,
coreDataModels: [CoreDataModel] = [],
scripts: [TargetScript] = [],
Expand All @@ -87,8 +93,11 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
self.entitlements = entitlements
self.settings = settings
self.sources = sources
self.sourcesPaths = sourcesPaths
self.resources = resources
self.resourcesPaths = resourcesPaths
self.copyFiles = copyFiles
self.copyFilesPaths = copyFilesPaths
self.headers = headers
self.coreDataModels = coreDataModels
self.scripts = scripts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ extension TuistGraph.CopyFileElement {
manifest: ProjectDescription.CopyFileElement,
generatorPaths: GeneratorPaths,
includeFiles: @escaping (AbsolutePath) -> Bool = { _ in true }
) throws -> [TuistGraph.CopyFileElement] {
func globFiles(_ path: AbsolutePath) throws -> [AbsolutePath] {
if FileHandler.shared.exists(path), !FileHandler.shared.isFolder(path) { return [path] }
) throws -> (files: [TuistGraph.CopyFileElement], path: AbsolutePath) {
func globFiles(_ path: AbsolutePath) throws -> (files: [AbsolutePath], path: AbsolutePath) {
if FileHandler.shared.exists(path), !FileHandler.shared.isFolder(path) { return ([path], path) }

let files = try FileHandler.shared.throwingGlob(AbsolutePath.root, glob: String(path.pathString.dropFirst()))
.filter(includeFiles)
Expand All @@ -26,37 +26,41 @@ extension TuistGraph.CopyFileElement {
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 !path.pathString.isGlobComponent {
// FIXME: This should be done in a linter.
logger.warning("No files found at: \(path.pathString)")
}
}
}

return files
return (files, path)
}

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

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

return [path]
return ([path], 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) }
let copyFiles = try globFiles(resolvedPath)
return (copyFiles.files.map { .file(path: $0, condition: condition?.asGraphCondition) }, copyFiles.path)
case let .folderReference(path: folderReferencePath, condition: condition):
let resolvedPath = try generatorPaths.resolve(path: folderReferencePath)
return folderReferences(resolvedPath).map { .folderReference(path: $0, condition: condition?.asGraphCondition) }
let copyFiles = folderReferences(resolvedPath)
return (copyFiles.files.map { .folderReference(path: $0, condition: condition?.asGraphCondition) }, copyFiles.path)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,23 @@ extension TuistGraph.CopyFilesAction {
/// - Parameters:
/// - manifest: Manifest representation of platform model.
/// - generatorPaths: Generator paths.
static func from(manifest: ProjectDescription.CopyFilesAction, generatorPaths: GeneratorPaths) throws -> TuistGraph
.CopyFilesAction
static func from(manifest: ProjectDescription.CopyFilesAction, generatorPaths: GeneratorPaths) throws -> (copyAction: TuistGraph
.CopyFilesAction, paths: [AbsolutePath])
{
var invalidResourceGlobs: [InvalidGlob] = []
var noneGlobFiles: [AbsolutePath] = []

let files: [TuistGraph.CopyFileElement] = try manifest.files.flatMap { manifest -> [TuistGraph.CopyFileElement] in
do {
let files = try TuistGraph.CopyFileElement.from(
manifest: manifest,
generatorPaths: generatorPaths,
includeFiles: { TuistGraph.Target.isResource(path: $0) }
)
return files.cleanPackages()
if !files.path.pathString.isGlobComponent {
noneGlobFiles.append(files.path)
}
return files.files.cleanPackages()
} catch let GlobError.nonExistentDirectory(invalidGlob) {
invalidResourceGlobs.append(invalidGlob)
return []
Expand All @@ -46,12 +51,12 @@ extension TuistGraph.CopyFilesAction {
throw CopyFilesManifestMapperError.invalidResourcesGlob(actionName: manifest.name, invalidGlobs: invalidResourceGlobs)
}

return TuistGraph.CopyFilesAction(
return (TuistGraph.CopyFilesAction(
name: manifest.name,
destination: TuistGraph.CopyFilesAction.Destination.from(manifest: manifest.destination),
subpath: manifest.subpath,
files: files
)
), noneGlobFiles)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ extension TuistGraph.FileElement {
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)")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ extension TuistGraph.ResourceFileElement {
manifest: ProjectDescription.ResourceFileElement,
generatorPaths: GeneratorPaths,
includeFiles: @escaping (AbsolutePath) -> Bool = { _ in true }
) throws -> [TuistGraph.ResourceFileElement] {
func globFiles(_ path: AbsolutePath, excluding: [String]) throws -> [AbsolutePath] {
) throws -> (resources: [TuistGraph.ResourceFileElement], path: AbsolutePath) {
func globFiles(_ path: AbsolutePath, excluding: [String]) throws -> (resources: [AbsolutePath], path: AbsolutePath) {
var excluded: Set<AbsolutePath> = []
for path in excluding {
let absolute = try AbsolutePath(validating: path)
Expand All @@ -33,47 +33,46 @@ extension TuistGraph.ResourceFileElement {
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)")
}
}

return files
return (files, path)
}

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

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

return [path]
return ([path], path)
}

switch manifest {
case let .glob(pattern, excluding, tags, condition):
let resolvedPath = try generatorPaths.resolve(path: pattern)
let excluding: [String] = try excluding.compactMap { try generatorPaths.resolve(path: $0).pathString }
return try globFiles(resolvedPath, excluding: excluding).map { ResourceFileElement.file(
let files = try globFiles(resolvedPath, excluding: excluding)
return (files.resources.map { ResourceFileElement.file(
path: $0,
tags: tags,
inclusionCondition: condition?.asGraphCondition
) }
) }, files.path)
case let .folderReference(folderReferencePath, tags, condition):
let resolvedPath = try generatorPaths.resolve(path: folderReferencePath)
return folderReferences(resolvedPath).map { ResourceFileElement.folderReference(
let files = folderReferences(resolvedPath)
return (files.resources.map { ResourceFileElement.folderReference(
path: $0,
tags: tags,
inclusionCondition: condition?.asGraphCondition
) }
) }, files.path)
}
}
}

0 comments on commit c9547a7

Please sign in to comment.