Skip to content

Commit 80ae5e4

Browse files
committed
Add support for passing wildcards to --targets
1 parent e3671f3 commit 80ae5e4

File tree

5 files changed

+127
-38
lines changed

5 files changed

+127
-38
lines changed

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetDiscoverer.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import Foundation
22

33
public enum BazelTargetDiscovererError: LocalizedError, Equatable {
4+
case noRulesProvided
45
case noTargetsDiscovered
6+
case noLocationsProvided
57

68
public var errorDescription: String? {
79
switch self {
10+
case .noRulesProvided: return "No rules provided!"
811
case .noTargetsDiscovered: return "No targets discovered!"
12+
case .noLocationsProvided: return "No locations provided!"
913
}
1014
}
1115
}
@@ -23,14 +27,22 @@ public enum BazelTargetDiscoverer {
2327
public static func discoverTargets(
2428
for rules: [TopLevelRuleType] = TopLevelRuleType.allCases,
2529
bazelWrapper: String = "bazel",
30+
locations: [String] = [],
2631
commandRunner: CommandRunner? = nil
2732
) throws -> [String] {
28-
let commandRunner = commandRunner ?? ShellCommandRunner()
33+
guard !rules.isEmpty else {
34+
throw BazelTargetDiscovererError.noRulesProvided
35+
}
2936

30-
let query = rules.map {
31-
"kind(\($0.rawValue), ...)"
37+
guard !locations.isEmpty else {
38+
throw BazelTargetDiscovererError.noLocationsProvided
3239
}
33-
.joined(separator: " + ")
40+
41+
let commandRunner = commandRunner ?? ShellCommandRunner()
42+
43+
let kindsQuery = "\"" + rules.map { $0.rawValue }.joined(separator: "|") + "\""
44+
let locationsQuery = locations.joined(separator: " union ")
45+
let query = "kind(\(kindsQuery), \(locationsQuery))"
3446

3547
let cmd = "\(bazelWrapper) query '\(query)' --output label"
3648

Sources/SourceKitBazelBSP/Server/BaseServerConfig.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ package struct BaseServerConfig: Equatable {
4949
// We need to post-process the target list provided by the user
5050
// because the queries will always return the "full" label.
5151
// e.g: "//foo/bar" -> "//foo/bar:bar"
52-
self.targets = targets.map { $0.toFullLabel() }
52+
// We need to also de-dupe them if the user passed wildcards in Serve.swift.
53+
self.targets = Set(targets.map { $0.toFullLabel() }).sorted()
5354
}
5455
}
5556

Sources/sourcekit-bazel-bsp/Commands/Serve.swift

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct Serve: ParsableCommand {
3131
@Option(
3232
parsing: .singleValue,
3333
help:
34-
"The *top level* Bazel application or test target that this should serve a BSP for. Can be specified multiple times. It's best to keep this list small if possible for performance reasons. If not specified, the server will try to discover top-level targets automatically."
34+
"The *top level* Bazel application or test target that this should serve a BSP for. Can be specified multiple times. Wildcards are supported (e.g. //foo/...). It's best to keep this list small if possible for performance reasons. If not specified, the server will try to discover top-level targets automatically."
3535
)
3636
var target: [String] = []
3737

@@ -69,30 +69,31 @@ struct Serve: ParsableCommand {
6969

7070
let targets: [String]
7171
if !target.isEmpty {
72-
targets = target
72+
var expandedTargets = [String]()
73+
var targetsToExpand = [String]()
74+
for _target in target {
75+
if _target.hasSuffix("...") {
76+
targetsToExpand.append(_target)
77+
} else {
78+
expandedTargets.append(_target)
79+
}
80+
}
81+
if !targetsToExpand.isEmpty {
82+
expandedTargets.append(contentsOf: try expandWildcardTargets(
83+
bazelWrapper: bazelWrapper,
84+
targets: targetsToExpand,
85+
topLevelRuleToDiscover: topLevelRuleToDiscover
86+
))
87+
}
88+
targets = expandedTargets
7389
} else {
7490
// If the user provided no specific targets, try to discover them
7591
// in the workspace.
76-
logger.warning(
77-
"No targets specified (--target)! Will now try to discover them. This can cause the BSP to perform poorly if we find too many targets. Prefer using --target explicitly if possible."
92+
targets = try expandWildcardTargets(
93+
bazelWrapper: bazelWrapper,
94+
targets: ["..."],
95+
topLevelRuleToDiscover: topLevelRuleToDiscover
7896
)
79-
do {
80-
let rulesToUse: [TopLevelRuleType]
81-
if !topLevelRuleToDiscover.isEmpty {
82-
rulesToUse = topLevelRuleToDiscover
83-
} else {
84-
rulesToUse = TopLevelRuleType.allCases
85-
}
86-
targets = try BazelTargetDiscoverer.discoverTargets(
87-
for: rulesToUse,
88-
bazelWrapper: bazelWrapper
89-
)
90-
} catch {
91-
logger.error(
92-
"Failed to initialize server: Could not discover targets. Please check your Bazel configuration or try specifying targets explicitly with `--target` instead. Failure: \(error)"
93-
)
94-
throw error
95-
}
9697
}
9798

9899
let config = BaseServerConfig(
@@ -103,7 +104,37 @@ struct Serve: ParsableCommand {
103104
compileTopLevel: compileTopLevel,
104105
indexBuildBatchSize: indexBuildBatchSize
105106
)
107+
108+
logger.debug("Initializing BSP with targets: \(targets)")
109+
106110
let server = SourceKitBazelBSPServer(baseConfig: config)
107111
server.run()
108112
}
113+
114+
private func expandWildcardTargets(
115+
bazelWrapper: String,
116+
targets: [String],
117+
topLevelRuleToDiscover: [TopLevelRuleType]
118+
) throws -> [String] {
119+
let targetsString = targets.joined(separator: ", ")
120+
logger.warning("Will expand wildcard targets (\(targetsString)). This can cause the BSP to perform poorly if we find too many targets. Prefer passing explicit targets via --targets if possible.")
121+
let rulesToUse: [TopLevelRuleType]
122+
if !topLevelRuleToDiscover.isEmpty {
123+
rulesToUse = topLevelRuleToDiscover
124+
} else {
125+
rulesToUse = TopLevelRuleType.allCases
126+
}
127+
do {
128+
return try BazelTargetDiscoverer.discoverTargets(
129+
for: rulesToUse,
130+
bazelWrapper: bazelWrapper,
131+
locations: targets
132+
)
133+
} catch {
134+
logger.error(
135+
"Failed to initialize server: Could not expand wildcard targets (\(targetsString)). Please check your Bazel configuration or try specifying targets explicitly with `--target` instead. Failure: \(error)"
136+
)
137+
throw error
138+
}
139+
}
109140
}

Tests/SourceKitBazelBSPTests/BazelTargetDiscovererTests.swift

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,60 +23,105 @@ import Testing
2323
@testable import SourceKitBazelBSP
2424

2525
struct BazelTargetDiscovererTests {
26-
@Test("Discovers single rule type with multiple targets")
26+
@Test
2727
func discoverSingleRuleTypeWithMultipleTargets() throws {
2828
let commandRunner = CommandRunnerFake()
2929
commandRunner.setResponse(
30-
for: "fakeBazel query 'kind(ios_application, ...)' --output label",
30+
for: "fakeBazel query 'kind(\"ios_application\", ...)' --output label",
3131
response: "//Example/HelloWorld:HelloWorld\n//Example/AnotherApp:AnotherApp\n"
3232
)
3333

3434
let targets = try BazelTargetDiscoverer.discoverTargets(
3535
for: [.iosApplication],
3636
bazelWrapper: "fakeBazel",
37-
commandRunner: commandRunner
37+
locations: ["..."],
38+
commandRunner: commandRunner,
3839
)
3940

4041
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/AnotherApp:AnotherApp"])
4142
#expect(commandRunner.commands.count == 1)
42-
#expect(commandRunner.commands[0].command == "fakeBazel query 'kind(ios_application, ...)' --output label")
43+
#expect(commandRunner.commands[0].command == "fakeBazel query 'kind(\"ios_application\", ...)' --output label")
4344
}
4445

45-
@Test("Discovers multiple rule types")
46+
@Test
4647
func discoverMultipleRuleTypes() throws {
4748
let commandRunner = CommandRunnerFake()
4849
commandRunner.setResponse(
49-
for: "fakeBazel query 'kind(ios_application, ...) + kind(ios_unit_test, ...)' --output label",
50+
for: "fakeBazel query 'kind(\"ios_application|ios_unit_test\", ...)' --output label",
5051
response: "//Example/HelloWorld:HelloWorld\n//Example/HelloWorldTests:HelloWorldTests\n"
5152
)
5253

5354
let targets = try BazelTargetDiscoverer.discoverTargets(
5455
for: [.iosApplication, .iosUnitTest],
5556
bazelWrapper: "fakeBazel",
56-
commandRunner: commandRunner
57+
locations: ["..."],
58+
commandRunner: commandRunner,
5759
)
5860

5961
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/HelloWorldTests:HelloWorldTests"])
6062
#expect(commandRunner.commands.count == 1)
6163
#expect(
6264
commandRunner.commands[0].command
63-
== "fakeBazel query 'kind(ios_application, ...) + kind(ios_unit_test, ...)' --output label"
65+
== "fakeBazel query 'kind(\"ios_application|ios_unit_test\", ...)' --output label"
6466
)
6567
}
6668

67-
@Test("Throws error when no targets found")
69+
@Test
70+
func discoverAtMultipleLocations() throws {
71+
let commandRunner = CommandRunnerFake()
72+
commandRunner.setResponse(
73+
for: "fakeBazel query 'kind(\"ios_application\", //A/... union //B/...)' --output label",
74+
response: "//Example/HelloWorld:HelloWorld\n//Example/AnotherApp:AnotherApp\n"
75+
)
76+
77+
let targets = try BazelTargetDiscoverer.discoverTargets(
78+
for: [.iosApplication],
79+
bazelWrapper: "fakeBazel",
80+
locations: ["//A/...", "//B/..."],
81+
commandRunner: commandRunner,
82+
)
83+
84+
#expect(targets == ["//Example/HelloWorld:HelloWorld", "//Example/AnotherApp:AnotherApp"])
85+
#expect(commandRunner.commands.count == 1)
86+
#expect(commandRunner.commands[0].command == "fakeBazel query 'kind(\"ios_application\", //A/... union //B/...)' --output label")
87+
}
88+
89+
@Test
6890
func throwsErrorWhenNoTargetsFound() throws {
6991
let commandRunner = CommandRunnerFake()
7092
commandRunner.setResponse(
71-
for: "fakeBazel query 'kind(ios_application, ...)' --output label",
93+
for: "fakeBazel query 'kind(\"ios_application\", ...)' --output label",
7294
response: "\n \n"
7395
)
7496

7597
#expect(throws: BazelTargetDiscovererError.noTargetsDiscovered) {
7698
try BazelTargetDiscoverer.discoverTargets(
7799
for: [.iosApplication],
78100
bazelWrapper: "fakeBazel",
79-
commandRunner: commandRunner
101+
locations: ["..."],
102+
commandRunner: commandRunner,
103+
)
104+
}
105+
}
106+
107+
@Test
108+
func failsIfNoRulesProvided() throws {
109+
#expect(throws: BazelTargetDiscovererError.noRulesProvided) {
110+
try BazelTargetDiscoverer.discoverTargets(
111+
for: [],
112+
bazelWrapper: "fakeBazel",
113+
locations: ["..."],
114+
)
115+
}
116+
}
117+
118+
@Test
119+
func failsIfNoLocationsProvided() throws {
120+
#expect(throws: BazelTargetDiscovererError.noLocationsProvided) {
121+
try BazelTargetDiscoverer.discoverTargets(
122+
for: [.iosApplication],
123+
bazelWrapper: "fakeBazel",
124+
locations: [],
80125
)
81126
}
82127
}

rules/setup_sourcekit_bsp.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ setup_sourcekit_bsp = rule(
7777
),
7878
# We avoid using label_list here to not trigger unnecessary bazel dependency graph checks.
7979
"targets": attr.string_list(
80-
doc = "The *top level* Bazel applications or test targets that this should serve a BSP for. It's best to keep this list small if possible for performance reasons. If not specified, the server will try to discover top-level targets automatically",
80+
doc = "The *top level* Bazel applications or test targets that this should serve a BSP for. Wildcards are supported (e.g. //foo/...). It's best to keep this list small if possible for performance reasons. If not specified, the server will try to discover top-level targets automatically",
8181
mandatory = True,
8282
),
8383
"bazel_wrapper": attr.string(

0 commit comments

Comments
 (0)