Skip to content

Commit

Permalink
Bazel support
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch committed Aug 18, 2024
1 parent 0690ce7 commit 9654ee2
Show file tree
Hide file tree
Showing 23 changed files with 2,471 additions and 96 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ DerivedData
Tests/Fixtures/.build/

# VSCode
.vscode/*
.vscode/*

# Bazel
bazel-*
12 changes: 12 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module(
name = "periphery",
version = "0.0.0",
compatibility_level = 1,
)

bazel_dep(name = "rules_swift", version = "2.1.1")
bazel_dep(name = "rules_apple", version = "3.8.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")

generated = use_extension("//bazel:extensions.bzl", "generated")
use_repo(generated, "periphery_generated")
1,934 changes: 1,934 additions & 0 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ var targets: [PackageDescription.Target] = [
dependencies: [
.target(name: "SyntaxAnalysis"),
.target(name: "Shared"),
.product(name: "SwiftIndexStore", package: "swift-indexstore")
.product(name: "SwiftIndexStore", package: "swift-indexstore"),
]
),
.target(
name: "SyntaxAnalysis",
dependencies: [
.target(name: "SourceGraph"),
.target(name: "Shared"),
.product(name: "SwiftSyntax", package: "swift-syntax")
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
]
),
.target(
Expand Down
4 changes: 4 additions & 0 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ struct ScanCommand: FrontendCommand {
@Option(help: "Project configuration for non-Apple build systems")
var genericProjectConfig: FilePath?

@Flag(help: "Enable Bazel project mode")
var bazel: Bool = defaultConfiguration.$bazel.defaultValue

private static let defaultConfiguration = Configuration()

func run() throws {
Expand Down Expand Up @@ -174,6 +177,7 @@ struct ScanCommand: FrontendCommand {
configuration.apply(\.$baseline, baseline)
configuration.apply(\.$writeBaseline, writeBaseline)
configuration.apply(\.$genericProjectConfig, genericProjectConfig)
configuration.apply(\.$bazel, bazel)

try scanBehavior.main { project in
try Scan().perform(project: project)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Frontend/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ final class Project {
return self.init(kind: .xcode(projectPath: path))
} else if let path = configuration.genericProjectConfig {
return self.init(kind: .generic(genericProjectConfig: path))
} else if Bazel.isSupported && configuration.bazel {
return self.init(kind: .bazel)
} else if SPM.isSupported {
return self.init(kind: .spm)
}
Expand All @@ -38,6 +40,8 @@ final class Project {
#endif
case .spm:
return try SPMProjectDriver.build()
case .bazel:
return try BazelProjectDriver.build()
case .generic(let genericProjectConfig):
return try GenericProjectDriver.build(genericProjectConfig: genericProjectConfig)
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Frontend/Scan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ final class Scan {
}
}

let driver = try setup(project)

// Output configuration after project setup as the driver may alter it.
if configuration.verbose {
let configYaml = try configuration.asYaml()
logger.debug("[configuration:begin]\n\(configYaml.trimmed)\n[configuration:end]")
}

let driver = try setup(project)
try build(driver)
try index(driver)
try analyze()
Expand Down
12 changes: 10 additions & 2 deletions Sources/Frontend/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ Logger.configureBuffering()
struct PeripheryCommand: FrontendCommand {
static let configuration = CommandConfiguration(
commandName: "periphery",
subcommands: [ScanCommand.self, CheckUpdateCommand.self, ClearCacheCommand.self, VersionCommand.self]
subcommands: [
ScanCommand.self,
CheckUpdateCommand.self,
ClearCacheCommand.self,
VersionCommand.self
]
)
}

signal(SIGINT) { _ in
let logger = Logger()
logger.warn("Termination can result in a corrupt index. Try the '--clean-build' flag if you get erroneous results, such as false-positives and incorrect source file locations.")
logger.warn(
"Termination can result in a corrupt index. Try the '--clean-build' flag if you get erroneous results, such as false-positives and incorrect source file locations.",
newlinePrefix: true // Print a newline after ^C
)
Shell.shared.interruptRunning()
exit(0)
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/PeripheryKit/Bazel/Bazel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation
import SystemPackage

public struct Bazel {
public static var isSupported: Bool {
FilePath("MODULE.bazel").exists || FilePath("WORKSPACE").exists
}
}
119 changes: 119 additions & 0 deletions Sources/PeripheryKit/Bazel/BazelProjectDriver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Foundation
import Shared
import SystemPackage

public class BazelProjectDriver: ProjectDriver {
public static func build() throws -> Self {
let configuration = Configuration.shared
configuration.bazel = false // Generic project mode is used for the actual scan.
configuration.reportExclude.append("**/bazel-out/**/*")
return self.init(configuration: configuration)
}

// TODO: Others? tvos, watchos, etc
private static let kinds = [
"apple_framework_packaging",
"ios_unit_test",
"ios_ui_test",
"ios_application",
// "swift_binary",
// "swift_test"
]

private let configuration: Configuration
private let shell: Shell
private let logger: Logger
private let fileManager: FileManager

private let outputPath = FilePath("/var/tmp/periphery_bazel")

private lazy var contextLogger: ContextualLogger = {
logger.contextualized(with: "bazel")
}()

required init(
configuration: Configuration = .shared,
shell: Shell = .shared,
logger: Logger = .init(),
fileManager: FileManager = .default
) {
self.configuration = configuration
self.shell = shell
self.logger = logger
self.fileManager = fileManager
}

public func build() throws {
guard let executablePath = Bundle.main.executablePath else {
fatalError("Expected executable path.")
}

try fileManager.createDirectory(at: outputPath.url, withIntermediateDirectories: true)

let configPath = outputPath.appending("periphery.yml")
try configuration.save(to: configPath)
contextLogger.debug("Configuration written to \(configPath)")

let buildPath = outputPath.appending("BUILD.bazel")
let deps = try queryTargets().joined(separator: ",\n")
let buildFileContents = """
load("@periphery//bazel/internal:scan.bzl", "scan")
scan(
name = "scan",
testonly = True,
config = "\(configPath)",
periphery_binary = "\(executablePath)",
visibility = [
"@periphery//bazel:package_group"
],
deps = [
\(deps)
],
)
"""

try buildFileContents.write(to: buildPath.url, atomically: true, encoding: .utf8)
contextLogger.debug("Build file written to \(buildPath)")

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

let status = try shell.execStatus([
"bazel",
"run",
"--ui_event_filters=-info,-debug,-warning",
"@periphery//bazel:scan"]
)

// The actual scan is performed by Bazel.
exit(status)
}

// MARK: - Private

private func queryTargets() throws -> [String] {
try shell
.exec([
"bazel",
"query",
"--noshow_progress",
"--ui_event_filters=-info,-debug,-warning",
query
])
.split(separator: "\n")
.map { "\"@@\($0)\"" }
}

private var query: String {
// TODO: Make configurable.
// TODO: Add option to filter labels.
let target = ["//..."]
let depsExpr = target.map { "deps(\($0))" }.joined(separator: " union ")
let kindsExpr = "kind('(\(Self.kinds.joined(separator: "|"))) rule', \(depsExpr))"
let filterExpr = "filter('^//.*', \(kindsExpr))"
return filterExpr
}
}
37 changes: 31 additions & 6 deletions Sources/PeripheryKit/Generic/GenericProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import SystemPackage

public final class GenericProjectDriver {
struct GenericConfig: Decodable {
let plistPaths: Set<String>
let indexstores: Set<String>
let plists: Set<String>
let xibs: Set<String>
let xcdatamodels: Set<String>
let xcmappingmodels: Set<String>
let testTargets: Set<String>
}

Expand All @@ -19,45 +23,66 @@ public final class GenericProjectDriver {
decoder.keyDecodingStrategy = .convertFromSnakeCase
let data = try Data(contentsOf: genericProjectConfig.url)
let config = try decoder.decode(GenericConfig.self, from: data)
let plistPaths = config.plistPaths.mapSet { FilePath.makeAbsolute($0) }
let plistPaths = config.plists.mapSet { FilePath.makeAbsolute($0) }
let xibPaths = config.xibs.mapSet { FilePath.makeAbsolute($0) }
let xcDataModelPaths = config.xcdatamodels.mapSet { FilePath.makeAbsolute($0) }
let xcMappingModelPaths = config.xcmappingmodels.mapSet { FilePath.makeAbsolute($0) }
let indexstorePaths = config.indexstores.mapSet { FilePath.makeAbsolute($0) }

return self.init(
indexstorePaths: indexstorePaths,
plistPaths: plistPaths,
xibPaths: xibPaths,
xcDataModelsPaths: xcDataModelPaths,
xcMappingModelsPaths: xcMappingModelPaths,
testTargets: config.testTargets,
configuration: .shared
)
}

private let indexstorePaths: Set<FilePath>
private let plistPaths: Set<FilePath>
private let xibPaths: Set<FilePath>
private let xcDataModelsPaths: Set<FilePath>
private let xcMappingModelsPaths: Set<FilePath>
private let testTargets: Set<String>
private let configuration: Configuration

private init(
indexstorePaths: Set<FilePath>,
plistPaths: Set<FilePath>,
xibPaths: Set<FilePath>,
xcDataModelsPaths: Set<FilePath>,
xcMappingModelsPaths: Set<FilePath>,
testTargets: Set<String>,
configuration: Configuration
) {
self.indexstorePaths = indexstorePaths
self.plistPaths = plistPaths
self.xibPaths = xibPaths
self.xcDataModelsPaths = xcDataModelsPaths
self.xcMappingModelsPaths = xcMappingModelsPaths
self.testTargets = testTargets
self.configuration = configuration
}
}

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

public func plan(logger: ContextualLogger) throws -> IndexPlan {
let excludedTestTargets = configuration.excludeTests ? testTargets : []
let collector = SourceFileCollector(
indexStorePaths: Set(configuration.indexStorePath),
indexStorePaths: Set(configuration.indexStorePath).union(indexstorePaths),
excludedTestTargets: excludedTestTargets,
logger: logger
)
let sourceFiles = try collector.collect()

return IndexPlan(
sourceFiles: sourceFiles,
plistPaths: plistPaths
plistPaths: plistPaths,
xibPaths: xibPaths,
xcDataModelPaths: xcDataModelsPaths,
xcMappingModelPaths: xcMappingModelsPaths
)
}
}
8 changes: 8 additions & 0 deletions Sources/PeripheryKit/ProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ public protocol ProjectDriver {
func build() throws
func plan(logger: ContextualLogger) throws -> IndexPlan
}

extension ProjectDriver {
public func build() throws {}

public func plan(logger: ContextualLogger) throws -> IndexPlan {
IndexPlan(sourceFiles: [:])
}
}
1 change: 1 addition & 0 deletions Sources/PeripheryKit/SPM/SPMProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Indexer
import Shared
import SwiftIndexStore
import SystemPackage

public final class SPMProjectDriver {
public static func build() throws -> Self {
let configuration = Configuration.shared
Expand Down
Loading

0 comments on commit 9654ee2

Please sign in to comment.