Skip to content

Commit

Permalink
Automatic target detection
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch committed Jul 7, 2024
1 parent 0b0407b commit 66c81bf
Show file tree
Hide file tree
Showing 197 changed files with 374 additions and 669 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ DerivedData
*.gcda
*.gcno
.swiftpm
Tests/Fixtures/.build/

# VSCode
.vscode/*
8 changes: 0 additions & 8 deletions .periphery.linux.yml

This file was deleted.

10 changes: 0 additions & 10 deletions .periphery.yml

This file was deleted.

45 changes: 0 additions & 45 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,43 +72,6 @@ var targets: [PackageDescription.Target] = [
],
path: "Tests/Shared"
),
.target(
name: "ExternalModuleFixtures",
path: "Tests/Fixtures/ExternalModuleFixtures"
),
.target(
name: "CrossModuleRetentionFixtures",
dependencies: [
.target(name: "CrossModuleRetentionSupportFixtures")
],
path: "Tests/Fixtures/CrossModuleRetentionFixtures"
),
.target(
name: "CrossModuleRetentionSupportFixtures",
path: "Tests/Fixtures/CrossModuleRetentionSupportFixtures"
),
.target(
name: "RetentionFixtures",
dependencies: [
.target(name: "ExternalModuleFixtures")
],
path: "Tests/Fixtures/RetentionFixtures"
),
.target(
name: "UnusedParameterFixtures",
path: "Tests/Fixtures/UnusedParameterFixtures",
swiftSettings: [
.unsafeFlags(["-suppress-warnings"]) // Suppress warnings from testLocalVariableAssignment
]
),
.target(
name: "TypeSyntaxInspectorFixtures",
path: "Tests/Fixtures/TypeSyntaxInspectorFixtures"
),
.target(
name: "DeclarationVisitorFixtures",
path: "Tests/Fixtures/DeclarationVisitorFixtures"
),
.testTarget(
name: "PeripheryTests",
dependencies: [
Expand Down Expand Up @@ -145,14 +108,6 @@ targets.append(contentsOf: [
.product(name: "XcodeProj", package: "XcodeProj"),
]
),
.target(
name: "ObjcAccessibleRetentionFixtures",
path: "Tests/Fixtures/ObjcAccessibleRetentionFixtures"
),
.target(
name: "ObjcAnnotatedRetentionFixtures",
path: "Tests/Fixtures/ObjcAnnotatedRetentionFixtures"
),
.testTarget(
name: "XcodeTests",
dependencies: [
Expand Down
18 changes: 0 additions & 18 deletions Sources/Frontend/Commands/ScanBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,6 @@ final class ScanBehavior {
logger.info(output, canQuiet: false)
logger.endInterval(interval)

if filteredResults.count > 0,
configuration.outputFormat.supportsAuxiliaryOutput {
logger.info(
colorize("\n* ", .boldGreen) +
colorize("Seeing false positives?", .bold) +

colorize("\n - ", .boldGreen) +
"Periphery only analyzes files that are members of the targets you specify." +
"\n References to declarations identified as unused may reside in files that are members of other targets, e.g test targets." +

colorize("\n - ", .boldGreen) +
"Periphery is a very precise tool, false positives often turn out to be correct after further investigation." +

colorize("\n - ", .boldGreen) +
"If it really is a false positive, please report it - https://github.com/peripheryapp/periphery/issues."
)
}

updateChecker.notifyIfAvailable()

if !filteredResults.isEmpty && configuration.strict {
Expand Down
80 changes: 36 additions & 44 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@ struct ScanCommand: FrontendCommand {
@Option(parsing: .upToNextOption, help: "File target mapping configuration file paths. For use with third-party build systems")
var fileTargetsPath: [FilePath] = defaultConfiguration.$fileTargetsPath.defaultValue

@Option(parsing: .upToNextOption, help: "Schemes that must be built in order to produce the targets passed to the --targets option. Xcode projects only")
@Option(parsing: .upToNextOption, help: "TODO") // TODO: Update, explain targets?
var schemes: [String] = defaultConfiguration.$schemes.defaultValue

@Option(parsing: .upToNextOption, help: "Target names to scan. Required for Xcode projects. Optional for Swift Package Manager projects, default behavior is to scan all targets defined in Package.swift")
var targets: [String] = defaultConfiguration.$targets.defaultValue

@Option(help: "Output format (allowed: \(OutputFormat.allValueStrings.joined(separator: ", ")))")
var format: OutputFormat = defaultConfiguration.$outputFormat.defaultValue

Expand Down Expand Up @@ -117,9 +114,6 @@ struct ScanCommand: FrontendCommand {
@Flag(help: "Only output results")
var quiet: Bool = defaultConfiguration.$quiet.defaultValue

@Option(help: "JSON package manifest path (obtained using `swift package describe --type json` or manually)")
var jsonPackageManifestPath: String?

@Option(help: "Baseline file path used to filter results")
var baseline: FilePath?

Expand All @@ -137,43 +131,41 @@ struct ScanCommand: FrontendCommand {

let configuration = Configuration.shared
configuration.guidedSetup = setup
configuration.apply(\.$workspace, workspace)
configuration.apply(\.$project, project)
configuration.apply(\.$fileTargetsPath, fileTargetsPath)
configuration.apply(\.$schemes, schemes)
configuration.apply(\.$targets, targets)
configuration.apply(\.$indexExclude, indexExclude)
configuration.apply(\.$reportExclude, reportExclude)
configuration.apply(\.$reportInclude, reportInclude)
configuration.apply(\.$outputFormat, format)
configuration.apply(\.$retainFiles, retainFiles)
configuration.apply(\.$retainPublic, retainPublic)
configuration.apply(\.$retainAssignOnlyProperties, retainAssignOnlyProperties)
configuration.apply(\.$retainAssignOnlyPropertyTypes, retainAssignOnlyPropertyTypes)
configuration.apply(\.$retainObjcAccessible, retainObjcAccessible)
configuration.apply(\.$retainObjcAnnotated, retainObjcAnnotated)
configuration.apply(\.$retainUnusedProtocolFuncParams, retainUnusedProtocolFuncParams)
configuration.apply(\.$retainSwiftUIPreviews, retainSwiftUIPreviews)
configuration.apply(\.$disableRedundantPublicAnalysis, disableRedundantPublicAnalysis)
configuration.apply(\.$disableUnusedImportAnalysis, disableUnusedImportAnalysis)
configuration.apply(\.$externalEncodableProtocols, externalEncodableProtocols)
configuration.apply(\.$externalCodableProtocols, externalCodableProtocols)
configuration.apply(\.$externalTestCaseClasses, externalTestCaseClasses)
configuration.apply(\.$verbose, verbose)
configuration.apply(\.$quiet, quiet)
configuration.apply(\.$disableUpdateCheck, disableUpdateCheck)
configuration.apply(\.$strict, strict)
configuration.apply(\.$indexStorePath, indexStorePath)
configuration.apply(\.$skipBuild, skipBuild)
configuration.apply(\.$skipSchemesValidation, skipSchemesValidation)
configuration.apply(\.$cleanBuild, cleanBuild)
configuration.apply(\.$buildArguments, buildArguments)
configuration.apply(\.$relativeResults, relativeResults)
configuration.apply(\.$retainCodableProperties, retainCodableProperties)
configuration.apply(\.$retainEncodableProperties, retainEncodableProperties)
configuration.apply(\.$jsonPackageManifestPath, jsonPackageManifestPath)
configuration.apply(\.$baseline, baseline)
configuration.apply(\.$writeBaseline, writeBaseline)
configuration.$workspace.assign(workspace)
configuration.$project.assign(project)
configuration.$fileTargetsPath.assign(fileTargetsPath)
configuration.$schemes.assign(schemes)
configuration.$indexExclude.assign(indexExclude)
configuration.$reportExclude.assign(reportExclude)
configuration.$reportInclude.assign(reportInclude)
configuration.$outputFormat.assign(format)
configuration.$retainFiles.assign(retainFiles)
configuration.$retainPublic.assign(retainPublic)
configuration.$retainAssignOnlyProperties.assign(retainAssignOnlyProperties)
configuration.$retainAssignOnlyPropertyTypes.assign(retainAssignOnlyPropertyTypes)
configuration.$retainObjcAccessible.assign(retainObjcAccessible)
configuration.$retainObjcAnnotated.assign(retainObjcAnnotated)
configuration.$retainUnusedProtocolFuncParams.assign(retainUnusedProtocolFuncParams)
configuration.$retainSwiftUIPreviews.assign(retainSwiftUIPreviews)
configuration.$disableRedundantPublicAnalysis.assign(disableRedundantPublicAnalysis)
configuration.$disableUnusedImportAnalysis.assign(disableUnusedImportAnalysis)
configuration.$externalEncodableProtocols.assign(externalEncodableProtocols)
configuration.$externalCodableProtocols.assign(externalCodableProtocols)
configuration.$externalTestCaseClasses.assign(externalTestCaseClasses)
configuration.$verbose.assign(verbose)
configuration.$quiet.assign(quiet)
configuration.$disableUpdateCheck.assign(disableUpdateCheck)
configuration.$strict.assign(strict)
configuration.$indexStorePath.assign(indexStorePath)
configuration.$skipBuild.assign(skipBuild)
configuration.$skipSchemesValidation.assign(skipSchemesValidation)
configuration.$cleanBuild.assign(cleanBuild)
configuration.$buildArguments.assign(buildArguments)
configuration.$relativeResults.assign(relativeResults)
configuration.$retainCodableProperties.assign(retainCodableProperties)
configuration.$retainEncodableProperties.assign(retainEncodableProperties)
configuration.$baseline.assign(baseline)
configuration.$writeBaseline.assign(writeBaseline)

try scanBehavior.main { project in
try Scan().perform(project: project)
Expand Down
39 changes: 2 additions & 37 deletions Sources/Frontend/SPMProjectSetupGuide.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import PeripheryKit
import Shared

final class SPMProjectSetupGuide: SetupGuideHelpers, ProjectSetupGuide {
private let configuration: Configuration

init(configuration: Configuration = .shared) {
self.configuration = configuration
super.init()
}

var projectKind: ProjectKind {
.spm
}
Expand All @@ -19,37 +12,9 @@ final class SPMProjectSetupGuide: SetupGuideHelpers, ProjectSetupGuide {
SPM.isSupported
}

func perform() throws {
let package = try SPM.Package.load()
let selection = try selectTargets(in: package)

if case let .some(targets) = selection {
configuration.targets = targets
}
}
func perform() {}

var commandLineOptions: [String] {
var options: [String] = []

if !configuration.targets.isEmpty {
options.append("--targets " + configuration.targets.map { "\"\($0)\"" }.joined(separator: ","))
}

return options
[]
}

// MARK: - Private

private func selectTargets(in package: SPM.Package) throws -> SetupSelection {
let targets = package.swiftTargets

guard !targets.isEmpty else {
throw PeripheryError.guidedSetupError(message: "Failed to identify any targets in package \(package.name)")
}

print(colorize("Select build targets to analyze:", .bold))
let targetNames = targets.map { $0.name }.sorted()
return select(multiple: targetNames, allowAll: true)
}

}
5 changes: 4 additions & 1 deletion Sources/Frontend/Scan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ final class Scan {
logger.info("\(asterisk) Indexing...")
}

let indexLogger = logger.contextualized(with: "index")
let sourceFiles = try driver.collect(logger: indexLogger)

let graph = SourceGraph.shared
try driver.index(graph: graph)
try driver.index(sourceFiles: sourceFiles, graph: graph, logger: indexLogger)
logger.endInterval(indexInterval)

let analyzeInterval = logger.beginInterval("analyze")
Expand Down
42 changes: 27 additions & 15 deletions Sources/PeripheryKit/Generic/GenericProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import Foundation
import SystemPackage
import Shared
import SourceGraph
import SwiftIndexStore

public final class GenericProjectDriver {
private enum FileKind: String {
case swift
case plist
}

Expand All @@ -14,8 +14,8 @@ public final class GenericProjectDriver {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let sourceFiles = try configuration.fileTargetsPath
.reduce(into: [FileKind: [FilePath: Set<IndexTarget>]]()) { result, mapPath in
let projectFiles = try configuration.fileTargetsPath
.reduce(into: [FileKind: Set<FilePath>]()) { result, mapPath in
guard mapPath.exists else {
throw PeripheryError.pathDoesNotExist(path: mapPath.string)
}
Expand All @@ -35,33 +35,45 @@ public final class GenericProjectDriver {
throw PeripheryError.unsupportedFileKind(path: path)
}

let indexTargets = value.mapSet { IndexTarget(name: $0) }
result[fileKind, default: [:]][path, default: []].formUnion(indexTargets)
result[fileKind, default: []].insert(path)
}
}

return self.init(sourceFiles: sourceFiles, configuration: configuration)
return self.init(projectFiles: projectFiles, configuration: configuration)
}

private let sourceFiles: [FileKind: [FilePath: Set<IndexTarget>]]
private let projectFiles: [FileKind: Set<FilePath>]
private let configuration: Configuration

private init(sourceFiles: [FileKind: [FilePath: Set<IndexTarget>]], configuration: Configuration) {
self.sourceFiles = sourceFiles
private init(projectFiles: [FileKind: Set<FilePath>], configuration: Configuration) {
self.projectFiles = projectFiles
self.configuration = configuration
}
}

extension GenericProjectDriver: ProjectDriver {
public func build() throws {}

public func index(graph: SourceGraph) throws {
if let swiftFiles = sourceFiles[.swift] {
try SwiftIndexer(sourceFiles: swiftFiles, graph: graph, indexStorePaths: configuration.indexStorePath).perform()
}
public func collect(logger: ContextualLogger) throws -> [SourceFile : [IndexUnit]] {
try SourceFileCollector(
indexStorePaths: configuration.indexStorePath,
logger: logger
).collect()
}

public func index(
sourceFiles: [SourceFile: [IndexUnit]],
graph: SourceGraph,
logger: ContextualLogger
) throws {
try SwiftIndexer(
sourceFiles: sourceFiles,
graph: graph,
logger: logger
).perform()

if let plistFiles = sourceFiles[.plist] {
try InfoPlistIndexer(infoPlistFiles: Set(plistFiles.keys), graph: graph).perform()
if let plistFiles = projectFiles[.plist] {
try InfoPlistIndexer(infoPlistFiles: plistFiles, graph: graph).perform()
}

graph.indexingComplete()
Expand Down
9 changes: 0 additions & 9 deletions Sources/PeripheryKit/Indexer/IndexTarget.swift

This file was deleted.

5 changes: 5 additions & 0 deletions Sources/PeripheryKit/Indexer/Indexer.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import SystemPackage
import Shared
import SourceGraph
import FilenameMatcher

public class Indexer {
Expand All @@ -16,4 +17,8 @@ public class Indexer {
let included = files.filter { !configuration.indexExcludeMatchers.anyMatch(filename: $0.string) }
return (included, files.subtracting(included))
}

func isRetained(_ file: SourceFile) -> Bool {
configuration.retainFilesMatchers.anyMatch(filename: file.path.string)
}
}
Loading

0 comments on commit 66c81bf

Please sign in to comment.