Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ By default, Periphery looks for the index store at `.build/debug/index/store`. T
bazel run @periphery -- scan --bazel
```

The `--bazel` option enables Bazel mode, which provides seamless integration with your project. It works by querying your project to identify all top-level targets, generating a hidden implementation of the [scan](https://github.com/peripheryapp/periphery/blob/master/bazel/rules.bzl) rule, and then invoking `bazel run`. You can filter the top-level targets with the `--bazel-filter <value>` option, where `<value>` will be passed as the first argument to Bazel's [filter](https://bazel.build/query/language#filter) operator. The generated query can be seen in the console with the `--verbose` option.
The `--bazel` option enables Bazel mode, which provides seamless integration with your project. It works by querying your project to identify all top-level targets, generating a hidden implementation of the [scan](https://github.com/peripheryapp/periphery/blob/master/bazel/rules.bzl) rule, and then invoking `bazel run`. You can filter the top-level targets with the `--bazel-filter <value>` option, where `<value>` will be passed as the first argument to Bazel's [filter](https://bazel.build/query/language#filter) operator. When the filter is an exact single target label match such as `//:app$`, Periphery skips the discovery query and uses that label directly. More complex regex filters continue to use the full workspace query. The generated query can be seen in the console with the `--verbose` option.

> [!TIP]
> By default, Periphery passes `--check_visibility=false` to `bazel run` to simplify integration, since the generated scan target references your project's targets which may not otherwise be visible. However, disabling visibility checking can invalidate Bazel's analysis cache, resulting in slower subsequent builds.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ struct ScanCommand: ParsableCommand {
@Flag(help: "Enable Bazel project mode")
var bazel: Bool = defaultConfiguration.$bazel.defaultValue

@Option(help: "Filter pattern applied to the Bazel top-level targets query")
@Option(help: "Filter pattern applied to the Bazel top-level targets query. Exact single-target label filters skip target discovery and use that label directly.")
var bazelFilter: String?

@Option(help: "Path to a global index store populated by Bazel. If provided, will be used instead of individual module stores.")
Expand Down
43 changes: 42 additions & 1 deletion Sources/ProjectDrivers/BazelProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,12 @@ public final class BazelProjectDriver: ProjectDriver {
// MARK: - Private

private func queryTargets() throws -> [String] {
try shell
if let filter = configuration.bazelFilter,
let exactTargetLabels = exactTargetLabels(filter: filter) {
return exactTargetLabels
}

return try shell
.exec(["bazel", "query", "\"\(query)\""])
.split(separator: "\n")
.map { "\"@@\($0)\"" }
Expand All @@ -145,4 +150,40 @@ public final class BazelProjectDriver: ProjectDriver {

return query
}

private func exactTargetLabels(filter: String) -> [String]? {
guard let label = exactQueryRoot(filter: filter) else {
return nil
}

return ["\"@@\(label)\""]
}

private func exactQueryRoot(filter: String) -> String? {
var pattern = filter.trimmingCharacters(in: .whitespacesAndNewlines)
guard !pattern.isEmpty else {
return nil
}

guard pattern.last == "$" else {
return nil
}

if pattern.first == "^" {
pattern.removeFirst()
}

pattern.removeLast()

let disallowedCharacters = CharacterSet(charactersIn: "^$.*+?[](){}|\\")
guard pattern.rangeOfCharacter(from: disallowedCharacters) == nil else {
return nil
}

guard pattern.hasPrefix("//"), !pattern.contains("...") else {
return nil
}

return pattern
}
}