Skip to content

Commit

Permalink
Bazel support (#786)
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch authored Aug 19, 2024
1 parent 1476ff3 commit 17d689b
Show file tree
Hide file tree
Showing 24 changed files with 2,502 additions and 97 deletions.
1 change: 1 addition & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
common --enable_bzlmod
1 change: 1 addition & 0 deletions .bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.3.0
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 @@ -62,7 +62,7 @@ var targets: [PackageDescription.Target] = [
dependencies: [
.target(name: "SyntaxAnalysis"),
.target(name: "Shared"),
.product(name: "SwiftIndexStore", package: "swift-indexstore")
.product(name: "SwiftIndexStore", package: "swift-indexstore"),
]
),
.target(
Expand All @@ -74,7 +74,8 @@ var targets: [PackageDescription.Target] = [
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
8 changes: 8 additions & 0 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ 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

@Option(help: "Filter pattern applied to the Bazel top-level targets query")
var bazelFilter: String?

private static let defaultConfiguration = Configuration()

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

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 @@ -11,6 +11,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 BazelProjectDriver.isSupported && configuration.bazel {
return self.init(kind: .bazel)
} else if SPM.isSupported {
return self.init(kind: .spm)
}
Expand All @@ -34,6 +36,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 @@ -25,12 +25,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
160 changes: 160 additions & 0 deletions Sources/ProjectDrivers/BazelProjectDriver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import Foundation
import Shared
import SystemPackage

public class BazelProjectDriver: ProjectDriver {
public static var isSupported: Bool {
FilePath("MODULE.bazel").exists || FilePath("WORKSPACE").exists
}

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

private static let topLevelKinds = [
// rules_apple, iOS
"ios_app_clip",
"ios_application",
"ios_extension",
"ios_imessage_application",
"ios_imessage_extension",
"ios_sticker_pack_extension",
"ios_ui_test",
"ios_unit_test",

// rules_apple, tvOS
"tvos_application",
"tvos_extension",
"tvos_ui_test",
"tvos_unit_test",

// rules_apple, watchOS
"watchos_application",
"watchos_extension",
"watchos_ui_test",
"watchos_unit_test",

// rules_apple, visionOS
"visionos_application",
"visionos_ui_test",
"visionos_unit_test",

// rules_apple, macOS
"macos_application",
"macos_command_line_application",
"macos_extension",
"macos_kernel_extension",
"macos_quick_look_plugin",
"macos_spotlight_importer",
"macos_xpc_service",
"macos_ui_test",
"macos_unit_test",

// rules_swift
"swift_binary",
"swift_test",
"swift_compiler_plugin",
]

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", query], stderr: false)
.split(separator: "\n")
.map { "\"@@\($0)\"" }
}

private var query: String {
let query = """
filter(
'^//.*',
kind(
'(\(Self.topLevelKinds.joined(separator: "|"))) rule',
deps(//...)
)
)
"""

if let pattern = configuration.bazelFilter {
return "filter('\(pattern)', \(query))"
}

return query
}
}
37 changes: 31 additions & 6 deletions Sources/ProjectDrivers/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/ProjectDrivers/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/ProjectDrivers/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 17d689b

Please sign in to comment.