Skip to content

Commit 2a41a5b

Browse files
authored
Add the ability to filter targets (#132)
1 parent bca394a commit 2a41a5b

File tree

6 files changed

+140
-12
lines changed

6 files changed

+140
-12
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ If this is undesirable, you can pass the `--compile-top-level` flag to make the
7878

7979
## Best Practices
8080

81-
- When working with large apps, consider being more explicit about the task you're going to do. This means that instead of importing the _entire app at all times_, try to import only a small group of test targets that you think will be required to perform the task. This will greatly increase the performance of the IDE.
81+
- When working with large apps, consider being more explicit about the task you're going to do. This means that instead of importing the _entire app at all times_ or running wide-reaching wildcards like `//...`, try to import only a small group of test targets that you think will be required to perform the task. This will greatly increase the performance of the IDE.
82+
- The BSP's many filtering arguments can be particularly useful for more complex cases.
8283
- For smaller apps, this doesn't make much difference and it should be fine to import the entire app.
83-
- Similarly, avoid wide-reaching wildcards like `//...`. Do so only at a smaller scale to avoid too many targets from being picked up.
8484

8585
## Troubleshooting
8686

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetQuerier.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,23 @@ final class BazelTargetQuerier {
9797

9898
// Collect the top-level targets -> collect these targets' dependencies
9999
let providedTargetsQuerySet = "set(\(userProvidedTargets.joined(separator: " ")))"
100+
101+
// Build exclusion clauses if filters are provided
102+
let topLevelExclusions = config.baseConfig.topLevelTargetsToExclude
103+
let dependencyExclusions = config.baseConfig.dependencyTargetsToExclude
104+
105+
let topLevelExceptClause =
106+
topLevelExclusions.isEmpty
107+
? "" : " except set(\(topLevelExclusions.joined(separator: " ")))"
108+
let dependencyExceptClause =
109+
dependencyExclusions.isEmpty
110+
? "" : " except set(\(dependencyExclusions.joined(separator: " ")))"
111+
100112
let topLevelTargetsQuery = """
101-
let topLevelTargets = kind("\(topLevelKindsFilter.joined(separator: "|"))", \(providedTargetsQuerySet)) in \
113+
let topLevelTargets = kind("\(topLevelKindsFilter.joined(separator: "|"))", \(providedTargetsQuerySet))\(topLevelExceptClause) in \
102114
$topLevelTargets \
103115
union \
104-
kind("\(dependencyKindsFilter.joined(separator: "|"))", deps($topLevelTargets))
116+
(kind("\(dependencyKindsFilter.joined(separator: "|"))", deps($topLevelTargets))\(dependencyExceptClause))
105117
"""
106118

107119
let cacheKey = "QUERY_TARGETS+\(topLevelTargetsQuery)"

Sources/SourceKitBazelBSP/Server/BaseServerConfig.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ package struct BaseServerConfig: Equatable {
3232
let compileTopLevel: Bool
3333
let topLevelRulesToDiscover: [TopLevelRuleType]
3434
let dependencyRulesToDiscover: [DependencyRuleType]
35+
let topLevelTargetsToExclude: [String]
36+
let dependencyTargetsToExclude: [String]
3537

3638
package init(
3739
bazelWrapper: String,
@@ -41,6 +43,8 @@ package struct BaseServerConfig: Equatable {
4143
compileTopLevel: Bool,
4244
topLevelRulesToDiscover: [TopLevelRuleType] = TopLevelRuleType.allCases,
4345
dependencyRulesToDiscover: [DependencyRuleType] = DependencyRuleType.allCases,
46+
topLevelTargetsToExclude: [String] = [],
47+
dependencyTargetsToExclude: [String] = []
4448
) {
4549
self.bazelWrapper = bazelWrapper
4650
self.targets = targets
@@ -49,5 +53,7 @@ package struct BaseServerConfig: Equatable {
4953
self.compileTopLevel = compileTopLevel
5054
self.topLevelRulesToDiscover = topLevelRulesToDiscover
5155
self.dependencyRulesToDiscover = dependencyRulesToDiscover
56+
self.topLevelTargetsToExclude = topLevelTargetsToExclude
57+
self.dependencyTargetsToExclude = dependencyTargetsToExclude
5258
}
5359
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ struct Serve: ParsableCommand {
6565
)
6666
var compileTopLevel: Bool = false
6767

68+
@Option(
69+
parsing: .singleValue,
70+
help:
71+
"A target pattern to exclude when discovering top-level targets. Can be specified multiple times."
72+
)
73+
var topLevelTargetToExclude: [String] = []
74+
75+
@Option(
76+
parsing: .singleValue,
77+
help:
78+
"A target pattern to exclude when discovering dependency targets. Can be specified multiple times."
79+
)
80+
var dependencyTargetToExclude: [String] = []
81+
6882
func run() throws {
6983
logger.info("`serve` invoked, initializing BSP server...")
7084

@@ -82,7 +96,9 @@ struct Serve: ParsableCommand {
8296
filesToWatch: filesToWatch,
8397
compileTopLevel: compileTopLevel,
8498
topLevelRulesToDiscover: topLevelRulesToDiscover,
85-
dependencyRulesToDiscover: dependencyRulesToDiscover
99+
dependencyRulesToDiscover: dependencyRulesToDiscover,
100+
topLevelTargetsToExclude: topLevelTargetToExclude,
101+
dependencyTargetsToExclude: dependencyTargetToExclude
86102
)
87103

88104
logger.debug("Initializing BSP with targets: \(targets)")

Tests/SourceKitBazelBSPTests/BazelTargetQuerierTests.swift

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,18 @@ struct BazelTargetQuerierTests {
4848
private static func makeInitializedConfig(
4949
bazelWrapper: String = "bazelisk",
5050
targets: [String] = ["//HelloWorld"],
51-
indexFlags: [String] = ["--config=test"]
51+
indexFlags: [String] = ["--config=test"],
52+
topLevelTargetsToExclude: [String] = [],
53+
dependencyTargetsToExclude: [String] = []
5254
) -> InitializedServerConfig {
5355
let baseConfig = BaseServerConfig(
5456
bazelWrapper: bazelWrapper,
5557
targets: targets,
5658
indexFlags: indexFlags,
5759
filesToWatch: nil,
58-
compileTopLevel: false
60+
compileTopLevel: false,
61+
topLevelTargetsToExclude: topLevelTargetsToExclude,
62+
dependencyTargetsToExclude: dependencyTargetsToExclude
5963
)
6064
return InitializedServerConfig(
6165
baseConfig: baseConfig,
@@ -89,7 +93,7 @@ struct BazelTargetQuerierTests {
8993
let config = Self.makeInitializedConfig()
9094

9195
let expectedCommand =
92-
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
96+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
9397
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
9498

9599
_ = try querier.cqueryTargets(
@@ -112,7 +116,7 @@ struct BazelTargetQuerierTests {
112116
let config = Self.makeInitializedConfig(targets: ["//HelloWorld", "//Tests"])
113117

114118
let expectedCommand =
115-
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld //Tests)) in $topLevelTargets union kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
119+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld //Tests)) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
116120
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
117121

118122
_ = try querier.cqueryTargets(
@@ -136,13 +140,13 @@ struct BazelTargetQuerierTests {
136140

137141
runnerMock.setResponse(
138142
for:
139-
"bazel --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union kind(\"swift_library|alias|source file\", deps($topLevelTargets))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto",
143+
"bazel --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union (kind(\"swift_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto",
140144
cwd: Self.mockRootUri,
141145
response: exampleCqueryOutput
142146
)
143147
runnerMock.setResponse(
144148
for:
145-
"bazel --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union kind(\"objc_library|alias|source file\", deps($topLevelTargets))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto",
149+
"bazel --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union (kind(\"objc_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto",
146150
cwd: Self.mockRootUri,
147151
response: exampleCqueryOutput
148152
)
@@ -178,7 +182,7 @@ struct BazelTargetQuerierTests {
178182
let config = Self.makeInitializedConfig()
179183

180184
let expectedCommand =
181-
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application|watchos_unit_test|_watchos_internal_unit_test_bundle\", set(//HelloWorld)) in $topLevelTargets union kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
185+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application|watchos_unit_test|_watchos_internal_unit_test_bundle\", set(//HelloWorld)) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
182186
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
183187

184188
_ = try querier.cqueryTargets(
@@ -193,6 +197,82 @@ struct BazelTargetQuerierTests {
193197
#expect(ranCommands[0].cwd == Self.mockRootUri)
194198
}
195199

200+
@Test
201+
func cqueryExcludesTopLevelTargets() throws {
202+
let runnerMock = CommandRunnerFake()
203+
let parserMock = BazelTargetQuerierParserFake()
204+
let querier = Self.makeQuerier(runner: runnerMock, parser: parserMock)
205+
let config = Self.makeInitializedConfig(
206+
topLevelTargetsToExclude: ["//HelloWorld:Excluded"]
207+
)
208+
209+
let expectedCommand =
210+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) except set(//HelloWorld:Excluded) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
211+
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
212+
213+
_ = try querier.cqueryTargets(
214+
config: config,
215+
supportedDependencyRuleTypes: DependencyRuleType.allCases,
216+
supportedTopLevelRuleTypes: [.iosApplication]
217+
)
218+
219+
let ranCommands = runnerMock.commands
220+
#expect(ranCommands.count == 1)
221+
#expect(ranCommands[0].command == expectedCommand)
222+
#expect(ranCommands[0].cwd == Self.mockRootUri)
223+
}
224+
225+
@Test
226+
func cqueryExcludesDependencyTargets() throws {
227+
let runnerMock = CommandRunnerFake()
228+
let parserMock = BazelTargetQuerierParserFake()
229+
let querier = Self.makeQuerier(runner: runnerMock, parser: parserMock)
230+
let config = Self.makeInitializedConfig(
231+
dependencyTargetsToExclude: ["//Libs/ExcludedLib:ExcludedLib"]
232+
)
233+
234+
let expectedCommand =
235+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)) except set(//Libs/ExcludedLib:ExcludedLib))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
236+
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
237+
238+
_ = try querier.cqueryTargets(
239+
config: config,
240+
supportedDependencyRuleTypes: DependencyRuleType.allCases,
241+
supportedTopLevelRuleTypes: [.iosApplication]
242+
)
243+
244+
let ranCommands = runnerMock.commands
245+
#expect(ranCommands.count == 1)
246+
#expect(ranCommands[0].command == expectedCommand)
247+
#expect(ranCommands[0].cwd == Self.mockRootUri)
248+
}
249+
250+
@Test
251+
func cqueryExcludesBothTopLevelAndDependencyTargets() throws {
252+
let runnerMock = CommandRunnerFake()
253+
let parserMock = BazelTargetQuerierParserFake()
254+
let querier = Self.makeQuerier(runner: runnerMock, parser: parserMock)
255+
let config = Self.makeInitializedConfig(
256+
topLevelTargetsToExclude: ["//HelloWorld:Excluded", "//HelloWorld:AlsoExcluded"],
257+
dependencyTargetsToExclude: ["//Libs/ExcludedLib:ExcludedLib"]
258+
)
259+
260+
let expectedCommand =
261+
"bazelisk --output_base=/path/to/output/base cquery \'let topLevelTargets = kind(\"ios_application\", set(//HelloWorld)) except set(//HelloWorld:Excluded //HelloWorld:AlsoExcluded) in $topLevelTargets union (kind(\"swift_library|objc_library|cc_library|alias|source file\", deps($topLevelTargets)) except set(//Libs/ExcludedLib:ExcludedLib))\' --noinclude_aspects --notool_deps --noimplicit_deps --output proto --config=test"
262+
runnerMock.setResponse(for: expectedCommand, cwd: Self.mockRootUri, response: exampleCqueryOutput)
263+
264+
_ = try querier.cqueryTargets(
265+
config: config,
266+
supportedDependencyRuleTypes: DependencyRuleType.allCases,
267+
supportedTopLevelRuleTypes: [.iosApplication]
268+
)
269+
270+
let ranCommands = runnerMock.commands
271+
#expect(ranCommands.count == 1)
272+
#expect(ranCommands[0].command == expectedCommand)
273+
#expect(ranCommands[0].cwd == Self.mockRootUri)
274+
}
275+
196276
// MARK: - Aquery Tests
197277

198278
@Test

rules/setup_sourcekit_bsp.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ def _setup_sourcekit_bsp_impl(ctx):
2222
for top_level_rule in ctx.attr.top_level_rules_to_discover:
2323
bsp_config_argv.append("--top-level-rule-to-discover")
2424
bsp_config_argv.append(top_level_rule)
25+
for target in ctx.attr.top_level_targets_to_exclude:
26+
bsp_config_argv.append("--top-level-target-to-exclude")
27+
bsp_config_argv.append(target)
28+
for target in ctx.attr.dependency_targets_to_exclude:
29+
bsp_config_argv.append("--dependency-target-to-exclude")
30+
bsp_config_argv.append(target)
2531
files_to_watch = ",".join(ctx.attr.files_to_watch)
2632
if files_to_watch:
2733
bsp_config_argv.append("--files-to-watch")
@@ -117,6 +123,14 @@ setup_sourcekit_bsp = rule(
117123
doc = "A list of top-level rule types to discover targets for (e.g. 'ios_application', 'ios_unit_test'). If not specified, all supported top-level rule types will be used for target discovery.",
118124
default = [],
119125
),
126+
"top_level_targets_to_exclude": attr.string_list(
127+
doc = "A list of target patterns to exclude from top-level targets in the cquery.",
128+
default = [],
129+
),
130+
"dependency_targets_to_exclude": attr.string_list(
131+
doc = "A list of target patterns to exclude from dependency targets in the cquery.",
132+
default = [],
133+
),
120134
"index_build_batch_size": attr.int(
121135
doc = "The number of targets to prepare in parallel. If not provided, will default to 1.",
122136
mandatory = False,

0 commit comments

Comments
 (0)