Skip to content
Merged
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
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ test_suite(
name = "clwb_tests",
tests = [
"//base:unit_tests",
"//base:unit_tests_kt",
"//clwb:headless_tests",
"//clwb:integration_tests",
"//clwb:unit_tests",
Expand Down
9 changes: 8 additions & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ bazel_dep(name = "rules_proto", version = "7.1.0")
bazel_dep(name = "protobuf", version = "33.1")
bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "rules_pkg", version = "1.1.0")
bazel_dep(name = "rules_bazel_integration_test", version = "0.34.0")
bazel_dep(name = "rules_jvm_external", version = "6.9")

# required for aspect tests
Expand All @@ -26,6 +25,14 @@ local_repository(
path = "clwb/tests/projects/virtual_includes/lib/external",
)

# custom rules_bazel_integration_test version, patches are required until we write our own version of these rules :c
bazel_dep(name = "rules_bazel_integration_test", version = "0.34.0")
single_version_override(
module_name = "rules_bazel_integration_test",
patch_strip = 1,
patches = ["//third_party/bazel/patches:rules_bazel_integration_test.patch"],
)

# custom rules kotlin version, patches are required
bazel_dep(name = "rules_kotlin", version = "2.2.0")
single_version_override(
Expand Down
122 changes: 22 additions & 100 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions base/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,13 @@ intellij_unit_test_suite(
],
)

test_suite(
name = "unit_tests_kt",
tests = [
"//base/tests/unittests/com/google/idea/blaze/base/dependencies:QueryBuilderTest",
],
)

stamped_plugin_xml(
name = "base_plugin_xml",
testonly = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,23 @@ public List<TargetInfo> doExpandDirectoryTargets(

@VisibleForTesting
public static String getQueryString(ImportRoots directories, boolean allowManualTargetsSync, WorkspacePathResolver pathResolver) {
StringBuilder targets = new StringBuilder();
targets.append(
directories.rootDirectories().stream()
.map(w -> TargetExpression.allFromPackageRecursive(w).toString())
.collect(joining(" + ")));
for (WorkspacePath excluded : directories.excludePathsForBazelQuery()) {
final var builder = new QueryBuilder()
.excludeNoIdeTag(true)
.excludeManualTag(!allowManualTargetsSync);

for (final var directory : directories.rootDirectories()) {
builder.includeTarget(TargetExpression.allFromPackageRecursive(directory));
}

for (final var directory : directories.excludePathsForBazelQuery()) {
// Bazel produces errors for paths that don't exist (e.g. bazel-out in a project that overrides the default symlinks),
// so only include paths that actually exist.
if (Files.exists(pathResolver.resolveToFile(excluded).toPath())) {
targets.append(" - " + TargetExpression.allFromPackageRecursive(excluded).toString());
if (Files.exists(pathResolver.resolveToFile(directory).toPath())) {
builder.excludeTarget(TargetExpression.allFromPackageRecursive(directory));
}
}

if (allowManualTargetsSync) {
return targets.toString();
}

// exclude 'manual' targets, which shouldn't be built when expanding wildcard target patterns
if (SystemInfo.isWindows) {
// TODO(b/201974254): Windows support for Bazel sync (see
// https://github.com/bazelbuild/intellij/issues/113).
return String.format("attr('tags', '^((?!manual).)*$', %s)", targets);
}
return String.format("attr(\"tags\", \"^((?!manual).)*$\", %s)", targets);
return builder.build();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2025 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.base.dependencies

import com.google.idea.blaze.base.model.primitives.TargetExpression

private const val MANUAL_TAG = "manual"
private const val NO_IDE_TAG = "no-ide"

class QueryBuilder {
private val includedTargets = mutableListOf<String>()
private val excludedTargets = mutableListOf<String>()

private val excludedTags = mutableListOf<String>()

fun excludeTarget(target: TargetExpression): QueryBuilder {
excludedTargets.add(target.toString().removePrefix("-"))
return this
}

fun excludeTargets(targets: Collection<TargetExpression>): QueryBuilder {
targets.forEach(::excludeTarget)
return this
}

fun includeTarget(target: TargetExpression): QueryBuilder {
if (target.isExcluded) {
excludeTarget(target)
} else {
includedTargets.add(target.toString())
}

return this
}

fun includeTargets(targets: Collection<TargetExpression>): QueryBuilder {
targets.forEach(::includeTarget)
return this
}

fun excludeManualTag(condition: Boolean = true): QueryBuilder {
if (condition) {
excludedTags.add(MANUAL_TAG)
}
return this
}

fun excludeNoIdeTag(condition: Boolean = true): QueryBuilder {
if (condition) {
excludedTags.add(NO_IDE_TAG)
}
return this
}

fun isEmpty(): Boolean {
return includedTargets.isEmpty()
}

fun build(): String {
if (includedTargets.isEmpty()) {
throw IllegalStateException("no targets included in query")
}

var query = includedTargets.joinToString(separator = "+") { "'$it'" }

if (excludedTargets.isNotEmpty()) {
val expression = excludedTargets.joinToString(separator = "-") { "'$it'" }
query = "$query-$expression"
}

if (excludedTags.isNotEmpty()) {
val expression = excludedTags.joinToString(separator = "|") { "($it)" }
query = "attr('tags','^((?!($expression)).)*$',$query)"
}

return query
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.BuildResult;
import com.google.idea.blaze.base.command.buildresult.BuildResult.Status;
import com.google.idea.blaze.base.dependencies.QueryBuilder;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WildcardTargetPattern;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
Expand Down Expand Up @@ -64,7 +65,6 @@
/** Expands wildcard target patterns into individual blaze targets. */
public class WildcardTargetExpander {

public static final String MANUAL_EXCLUDE_TAG = "^((?!manual).)*$";
private static final BoolExperiment filterByRuleType =
new BoolExperiment("blaze.build.filter.by.rule.type", true);

Expand Down Expand Up @@ -207,7 +207,10 @@ private static ExpandedTargetsResult queryIndividualTargets(
Predicate<String> handledRulesPredicate,
List<TargetExpression> targetPatterns,
boolean excludeManualTargets) {
String query = queryString(targetPatterns, excludeManualTargets);
final var query = new QueryBuilder()
.includeTargets(targetPatterns)
.excludeManualTag(excludeManualTargets)
.excludeNoIdeTag(true);
if (query.isEmpty()) {
// will be empty if there are no non-excluded targets
return new ExpandedTargetsResult(ImmutableList.of(), BuildResult.SUCCESS);
Expand All @@ -216,7 +219,7 @@ private static ExpandedTargetsResult queryIndividualTargets(
BlazeCommand.builder(buildBinary, BlazeCommandName.QUERY, project)
.addBlazeFlags(BlazeFlags.KEEP_GOING)
.addBlazeFlags("--output=label_kind")
.addBlazeFlags(query);
.addBlazeFlags(query.build());

// it's fine to include wildcards here; they're guaranteed not to clash with actual labels.
Set<String> explicitTargets =
Expand Down Expand Up @@ -244,34 +247,4 @@ private static Predicate<String> handledRuleTypes(ProjectViewSet projectViewSet)
return LanguageSupport.createWorkspaceLanguageSettings(projectViewSet)
.getAvailableTargetKinds();
}

private static String queryString(List<TargetExpression> targets, boolean excludeManualTargets) {
StringBuilder builder = new StringBuilder();
for (TargetExpression target : targets) {
boolean excluded = target.isExcluded();
if (builder.length() == 0) {
if (excluded) {
continue; // an excluded target at the start of the list has no effect
}
builder.append("'").append(target).append("'");
} else {
if (excluded) {
builder.append(" - ");
// trim leading '-'
String excludedTarget = target.toString();
builder.append("'").append(excludedTarget, 1, excludedTarget.length()).append("'");
} else {
builder.append(" + ");
builder.append("'").append(target).append("'");
}
}
}
String targetList = builder.toString();
if (targetList.isEmpty()) {
return targetList;
}
return excludeManualTargets
? String.format("attr('tags', '%s', %s)", MANUAL_EXCLUDE_TAG, targetList)
: targetList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
load("//build_defs:intellij_unit_test.bzl", "intellij_unit_test")

intellij_unit_test(
test = "QueryBuilderTest.kt",
deps = ["//base:plugin_library"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2025 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.base.dependencies

import com.google.common.truth.Truth
import com.google.idea.blaze.base.model.primitives.TargetExpression
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class QueryBuilderTest {

@Test
fun emptyQueryThrows() {
assertThrows(IllegalStateException::class.java) {
QueryBuilder().build()
}

assertThrows(IllegalStateException::class.java) {
QueryBuilder().excludeTarget("//some:target".asTargetExpression()).build()
}
}

@Test
fun includeManyTargets() {
val query = QueryBuilder()
.includeTarget("//some:target1".asTargetExpression())
.includeTarget("//some:target2".asTargetExpression())
.build()

Truth.assertThat(query).isEqualTo("'//some:target1'+'//some:target2'")
}

@Test
fun excludeManyTargets() {
val query = QueryBuilder()
.includeTarget("//some:target1".asTargetExpression())
.includeTarget("//some:target2".asTargetExpression())
.excludeTarget("//some:target3".asTargetExpression())
.excludeTarget("//some:target4".asTargetExpression())
.build()

Truth.assertThat(query).isEqualTo("'//some:target1'+'//some:target2'-'//some:target3'-'//some:target4'")
}

@Test
fun excludeTags() {
val query = QueryBuilder()
.includeTarget("//some:target".asTargetExpression())
.excludeManualTag()
.excludeNoIdeTag()
.build()

Truth.assertThat(query).isEqualTo("attr('tags','^((?!((manual)|(no-ide))).)*$','//some:target')")
}

private fun String.asTargetExpression(): TargetExpression {
return TargetExpression.fromString(this)
}

private fun assertThrows(exception: Class<*>, block: () -> Unit) {
try {
block()
fail("expected exception of type $exception")
} catch (e: Exception) {
Truth.assertThat(e).isInstanceOf(exception)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -390,18 +390,7 @@ public void expandAndShardTargets_expandWildcardTargets() {
@Test
public void expandAndShardTargets_expandWildcardTargetsNoExcludeManualTag() {
String expectedLabel1 = "//java/com/google:one";
FakeWildCardTargetExpanderBlazeInvoker commandInvoker = new FakeWildCardTargetExpanderBlazeInvoker() {

@Override
public InputStream invokeQuery(BlazeCommand.Builder blazeCommandBuilder, BlazeContext context) throws BuildException {
// We need to confirm within the query runner because there are no public methods currently to
// perform this check downstream.
for (String argument : blazeCommandBuilder.build().toArgumentList()) {
assertFalse(argument.contains(WildcardTargetExpander.MANUAL_EXCLUDE_TAG));
}
return super.invokeQuery(blazeCommandBuilder, context);
}
};
FakeWildCardTargetExpanderBlazeInvoker commandInvoker = new FakeWildCardTargetExpanderBlazeInvoker();

fakeWildCardTargetExpanderExternalTaskProvider
.setReturnVal(0)
Expand Down Expand Up @@ -432,16 +421,7 @@ public InputStream invokeQuery(BlazeCommand.Builder blazeCommandBuilder, BlazeCo
@Test
public void expandAndShardTargets_expandWildcardTargetsIncludesManualTag() {
String expectedLabel1 = "//java/com/google:one";
FakeWildCardTargetExpanderBlazeInvoker blazeInvoker = new FakeWildCardTargetExpanderBlazeInvoker() {

@Override
public InputStream invokeQuery(BlazeCommand.Builder blazeCommandBuilder, BlazeContext context) throws BuildException {
// We need to confirm within the query runner because there are no public methods currently to
// perform this check downstream.
assertTrue(blazeCommandBuilder.build().toArgumentList().stream().anyMatch(argument -> argument.contains(WildcardTargetExpander.MANUAL_EXCLUDE_TAG)));
return super.invokeQuery(blazeCommandBuilder, context);
}
};
FakeWildCardTargetExpanderBlazeInvoker blazeInvoker = new FakeWildCardTargetExpanderBlazeInvoker();

fakeWildCardTargetExpanderExternalTaskProvider
.setReturnVal(0)
Expand Down
4 changes: 2 additions & 2 deletions build_defs/intellij_plugin_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def intellij_plugin_library(name, srcs = None, deps = None, **kwargs):
kt_jvm_library(
name = name + "_ktlib",
srcs = srcs,
deps = deps + ["//intellij_platform_sdk:plugin_api",],
deps = deps + ["//intellij_platform_sdk:plugin_api"],
visibility = ["//visibility:private"],
)

Expand All @@ -133,4 +133,4 @@ def intellij_plugin_library(name, srcs = None, deps = None, **kwargs):
name = name,
deps = deps,
**kwargs
)
)
Loading