Skip to content

Commit

Permalink
Refactor generic project support, closes #778
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch committed Aug 4, 2024
1 parent 49eb57d commit a308b12
Show file tree
Hide file tree
Showing 37 changed files with 376 additions and 335 deletions.
29 changes: 12 additions & 17 deletions Sources/Frontend/Commands/ScanBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,22 @@ final class ScanBehavior {

func main(_ block: (Project) throws -> [ScanResult]) -> Result<(), PeripheryError> {
logger.contextualized(with: "version").debug(PeripheryVersion)

let project: Project

if configuration.guidedSetup {
do {
do {
logger.debug(SwiftVersion.current.fullVersion)
try SwiftVersion.current.validateVersion()

if configuration.guidedSetup {
project = try GuidedSetup().perform()
} catch let error as PeripheryError {
return .failure(error)
} catch {
return .failure(.underlyingError(error))
}
} else {
project = Project.identify()

do {
// Guided setup performs validation itself once the type has been determined.
try project.validateEnvironment()
} catch let error as PeripheryError {
return .failure(error)
} catch {
return .failure(.underlyingError(error))
} else {
project = try Project.identify()
}
} catch let error as PeripheryError {
return .failure(error)
} catch {
return .failure(.underlyingError(error))
}

let updateChecker = UpdateChecker()
Expand Down
10 changes: 5 additions & 5 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ struct ScanCommand: FrontendCommand {
var config: String?

@Option(help: "Path to your project's .xcodeproj or .xcworkspace")
var project: String?

@Option(parsing: .upToNextOption, help: "File target mapping configuration file paths. For use with third-party build systems")
var fileTargetsPath: [FilePath] = defaultConfiguration.$fileTargetsPath.defaultValue
var project: FilePath?

@Option(parsing: .upToNextOption, help: "Schemes to build. All targets built by these schemes will be scanned")
var schemes: [String] = defaultConfiguration.$schemes.defaultValue
Expand Down Expand Up @@ -126,6 +123,9 @@ struct ScanCommand: FrontendCommand {
@Option(help: "Baseline file path where results are written. Pass the same path to '--baseline' in subsequent scans to exclude the results recorded in the baseline.")
var writeBaseline: FilePath?

@Option(help: "Project configuration for non-Apple build systems")
var genericProjectConfig: FilePath?

private static let defaultConfiguration = Configuration()

func run() throws {
Expand All @@ -138,7 +138,6 @@ struct ScanCommand: FrontendCommand {
let configuration = Configuration.shared
configuration.guidedSetup = setup
configuration.apply(\.$project, project)
configuration.apply(\.$fileTargetsPath, fileTargetsPath)
configuration.apply(\.$schemes, schemes)
configuration.apply(\.$indexExclude, indexExclude)
configuration.apply(\.$reportExclude, reportExclude)
Expand Down Expand Up @@ -174,6 +173,7 @@ struct ScanCommand: FrontendCommand {
configuration.apply(\.$jsonPackageManifestPath, jsonPackageManifestPath)
configuration.apply(\.$baseline, baseline)
configuration.apply(\.$writeBaseline, writeBaseline)
configuration.apply(\.$genericProjectConfig, genericProjectConfig)

try scanBehavior.main { project in
try Scan().perform(project: project)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Frontend/CommonSetupGuide.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Shared

final class CommonSetupGuide: SetupGuideHelpers, SetupGuide {
final class CommonSetupGuide: SetupGuideHelpers {
private let configuration: Configuration

required init(configuration: Configuration = .shared) {
Expand Down
34 changes: 20 additions & 14 deletions Sources/Frontend/GuidedSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,43 @@ final class GuidedSetup: SetupGuideHelpers {
func perform() throws -> Project {
print(colorize("Welcome to Periphery!", .boldGreen))
print("This guided setup will help you select the appropriate configuration for your project.\n")
var projectGuides: [ProjectSetupGuide] = [SPMProjectSetupGuide()]

var projectGuides: [SetupGuide] = []

if let guide = SPMProjectSetupGuide.detect() {
projectGuides.append(guide)
}

#if canImport(XcodeSupport)
projectGuides.append(XcodeProjectSetupGuide())
if let guide = XcodeProjectSetupGuide.detect() {
projectGuides.append(guide)
}
#endif

let supportedProjectGuides = projectGuides.filter { $0.isSupported }
var projectGuide_: ProjectSetupGuide?
var projectGuide_: SetupGuide?

if supportedProjectGuides.count > 1 {
if projectGuides.count > 1 {
print(colorize("Please select which project to use:", .bold))
let kindName = select(single: supportedProjectGuides.map { $0.projectKind.rawValue })
projectGuide_ = supportedProjectGuides.first { $0.projectKind.rawValue == kindName }
let kindName = select(single: projectGuides.map { $0.projectKindName })
projectGuide_ = projectGuides.first { $0.projectKindName == kindName }
print("")
} else {
projectGuide_ = supportedProjectGuides.first
projectGuide_ = projectGuides.first
}

guard let projectGuide = projectGuide_ else {
fatalError("Failed to identify project type.")
}

let project = Project(kind: projectGuide.projectKind)
try project.validateEnvironment()

print(colorize("*", .boldGreen) + " Inspecting project...")

let kind = try projectGuide.perform()
let project = Project(kind: kind)

let commonGuide = CommonSetupGuide()
let guides: [SetupGuide] = [projectGuide, commonGuide]
try guides.forEach { try $0.perform() }
let options = Array(guides.map { $0.commandLineOptions }.joined())
try commonGuide.perform()

let options = projectGuide.commandLineOptions + commonGuide.commandLineOptions
var shouldSave = false

if configuration.hasNonDefaultValues {
Expand Down
43 changes: 10 additions & 33 deletions Sources/Frontend/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import XcodeSupport
#endif

final class Project {
static func identify() -> Self {
static func identify() throws -> Self {
let configuration = Configuration.shared

if configuration.project != nil {
return self.init(kind: .xcode)
} else if !configuration.fileTargetsPath.isEmpty {
return self.init(kind: .generic)
if let path = configuration.project {
return self.init(kind: .xcode(projectPath: path))
} else if let path = configuration.genericProjectConfig {
return self.init(kind: .generic(genericProjectConfig: path))
} else if SPM.isSupported {
return self.init(kind: .spm)
}

return self.init(kind: .xcode)
throw PeripheryError.usageError("Failed to identify project kind.")
}

let kind: ProjectKind
Expand All @@ -28,41 +28,18 @@ final class Project {
self.kind = kind
}

func validateEnvironment() throws {
let logger = Logger()

logger.debug(SwiftVersion.current.fullVersion)
try SwiftVersion.current.validateVersion()

switch kind {
case .xcode:
#if canImport(XcodeSupport)
do {
let xcodebuild = Xcodebuild()
logger.debug(try xcodebuild.version())
} catch {
throw PeripheryError.xcodebuildNotConfigured
}
#else
fatalError("Xcode projects are not supported on this platform.")
#endif
default:
break
}
}

func driver() throws -> ProjectDriver {
switch kind {
case .xcode:
case .xcode(let projectPath):
#if canImport(XcodeSupport)
return try XcodeProjectDriver.build()
return try XcodeProjectDriver.build(projectPath: projectPath)
#else
fatalError("Xcode projects are not supported on this platform.")
#endif
case .spm:
return try SPMProjectDriver.build()
case .generic:
return try GenericProjectDriver.build()
case .generic(let genericProjectConfig):
return try GenericProjectDriver.build(genericProjectConfig: genericProjectConfig)
}
}
}
15 changes: 9 additions & 6 deletions Sources/Frontend/SPMProjectSetupGuide.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import SystemPackage
import PeripheryKit
import Shared

final class SPMProjectSetupGuide: SetupGuideHelpers, ProjectSetupGuide {
var projectKind: ProjectKind {
.spm
final class SPMProjectSetupGuide: SetupGuideHelpers, SetupGuide {
static func detect() -> Self? {
guard SPM.isSupported else { return nil }
return Self()
}

var isSupported: Bool {
SPM.isSupported
var projectKindName: String {
"Swift Project Manager"
}

func perform() {}
func perform() throws -> ProjectKind {
.spm
}

var commandLineOptions: [String] {
[]
Expand Down
37 changes: 30 additions & 7 deletions Sources/Frontend/Scan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import Foundation
import Shared
import PeripheryKit
import SourceGraph
import Indexer

final class Scan {
private let configuration: Configuration
private let logger: Logger
private let graph = SourceGraph.shared

required init(configuration: Configuration = .shared, logger: Logger = .init()) {
self.configuration = configuration
Expand All @@ -27,44 +29,65 @@ final class Scan {
logger.debug("[configuration:begin]\n\(configYaml.trimmed)\n[configuration:end]")
}

let driver = try setup(project)
try build(driver)
try index(driver)
try analyze()
return buildResults()
}

// MARK: - Private

private func setup(_ project: Project) throws -> ProjectDriver {
let driverSetupInterval = logger.beginInterval("driver:setup")

if configuration.outputFormat.supportsAuxiliaryOutput {
let asterisk = colorize("*", .boldGreen)
logger.info("\(asterisk) Inspecting project...")
}

let driverPrepareInterval = logger.beginInterval("driver:prepare")
let driver = try project.driver()
logger.endInterval(driverPrepareInterval)
logger.endInterval(driverSetupInterval)
return driver
}

private func build(_ driver: ProjectDriver) throws {
let driverBuildInterval = logger.beginInterval("driver:build")
try driver.build()
logger.endInterval(driverBuildInterval)
}

private func index(_ driver: ProjectDriver) throws {
let indexInterval = logger.beginInterval("index")

if configuration.outputFormat.supportsAuxiliaryOutput {
let asterisk = colorize("*", .boldGreen)
logger.info("\(asterisk) Indexing...")
}

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

let graph = SourceGraph.shared
try driver.index(sourceFiles: sourceFiles, graph: graph, logger: indexLogger)
let plan = try driver.plan(logger: indexLogger)
let pipeline = IndexPipeline(plan: plan, graph: graph, logger: indexLogger)
try pipeline.perform()
logger.endInterval(indexInterval)
}

private func analyze() throws {
let analyzeInterval = logger.beginInterval("analyze")

if configuration.outputFormat.supportsAuxiliaryOutput {
let asterisk = colorize("*", .boldGreen)
logger.info("\(asterisk) Analyzing...")
}

try SourceGraphMutatorRunner.perform(graph: graph)
logger.endInterval(analyzeInterval)
}

private func buildResults() -> [ScanResult] {
let resultInterval = logger.beginInterval("result:build")
let results = ScanResultBuilder.build(for: graph)
logger.endInterval(resultInterval)

return results
}
}
41 changes: 41 additions & 0 deletions Sources/Indexer/IndexPipeline.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation
import Shared
import SourceGraph

public struct IndexPipeline {
private let plan: IndexPlan
private let graph: SourceGraph
private let logger: ContextualLogger

public init(plan: IndexPlan, graph: SourceGraph, logger: ContextualLogger) {
self.plan = plan
self.graph = graph
self.logger = logger
}

public func perform() throws {
try SwiftIndexer(
sourceFiles: plan.sourceFiles,
graph: graph,
logger: logger
).perform()

if !plan.plistPaths.isEmpty {
try InfoPlistIndexer(infoPlistFiles: plan.plistPaths, graph: graph).perform()
}

if !plan.xibPaths.isEmpty {
try XibIndexer(xibFiles: plan.xibPaths, graph: graph).perform()
}

if !plan.xcDataModelPaths.isEmpty {
try XCDataModelIndexer(files: plan.xcDataModelPaths, graph: graph).perform()
}

if !plan.xcMappingModelPaths.isEmpty {
try XCMappingModelIndexer(files: plan.xcMappingModelPaths, graph: graph).perform()
}

graph.indexingComplete()
}
}
25 changes: 25 additions & 0 deletions Sources/Indexer/IndexPlan.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation
import SystemPackage
import SourceGraph

public struct IndexPlan {
public let sourceFiles: [SourceFile : [IndexUnit]]
public let plistPaths: Set<FilePath>
public let xibPaths: Set<FilePath>
public let xcDataModelPaths: Set<FilePath>
public let xcMappingModelPaths: Set<FilePath>

public init(
sourceFiles: [SourceFile : [IndexUnit]],
plistPaths: Set<FilePath> = [],
xibPaths: Set<FilePath> = [],
xcDataModelPaths: Set<FilePath> = [],
xcMappingModelPaths: Set<FilePath> = []
) {
self.sourceFiles = sourceFiles
self.plistPaths = plistPaths
self.xibPaths = xibPaths
self.xcDataModelPaths = xcDataModelPaths
self.xcMappingModelPaths = xcMappingModelPaths
}
}
Loading

0 comments on commit a308b12

Please sign in to comment.