Skip to content
Draft
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
6 changes: 3 additions & 3 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ build --tool_java_language_version=21 --tool_java_runtime_version=21

# Delete test data packages, needed for bazel integration tests. Update by running the following command:
# bazel run @rules_bazel_integration_test//tools:update_deleted_packages
build --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib
query --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib
build --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/cmake,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib
query --deleted_packages=clwb/tests/projects/clang_cl,clwb/tests/projects/clang_cl/main,clwb/tests/projects/execution/main,clwb/tests/projects/external_includes/main,clwb/tests/projects/llvm_toolchain/main,clwb/tests/projects/llvm_toolchain/wasm,clwb/tests/projects/protobuf,clwb/tests/projects/protobuf/main,clwb/tests/projects/protobuf/proto,clwb/tests/projects/simple/main,clwb/tests/projects/target_compatible/main,clwb/tests/projects/virtual_includes/lib/cmake,clwb/tests/projects/virtual_includes/lib/impl_deps,clwb/tests/projects/virtual_includes/lib/raw_files,clwb/tests/projects/virtual_includes/lib/strip_absolut,clwb/tests/projects/virtual_includes/lib/strip_relative,clwb/tests/projects/virtual_includes/main,examples/cpp/simple_project/src,examples/cpp/simple_project/src/lib,examples/python/simple_code_generator/example,examples/python/simple_code_generator/generated,examples/python/simple_code_generator/lib,examples/python/simple_code_generator/rules,examples/python/with_numpy,examples/python/with_numpy/app,examples/python/with_numpy/lib

common --enable_bzlmod
common --noincompatible_disallow_empty_glob
Expand All @@ -16,6 +16,6 @@ test --zip_undeclared_test_outputs
common --incompatible_use_plus_in_repo_names

# Required for CLion integration tests on windows
startup --windows_enable_symlinks
startup --nowindows_enable_symlinks
build --enable_runfiles
build --legacy_external_runfiles
5 changes: 5 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ bazel_dep(
version = "0.2.11",
dev_dependency = True,
)
bazel_dep(
name = "rules_foreign_cc",
version = "0.13.0",
dev_dependency = True,
)
bazel_dep(
name = "rules_bazel_integration_test",
version = "0.34.0",
Expand Down
376 changes: 376 additions & 0 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions aspect/testing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ test_suite(
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctest:CcTestTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain:CcToolchainTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/coptsmakevars:CoptsMakeVarsTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/foreigncc:ForeignCcTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/general/alias:AliasTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/general/analysistest:AnalysisTestTest",
"//aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts:ArtifactTest",
Expand Down
17 changes: 17 additions & 0 deletions aspect/testing/rules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
load("@protobuf//bazel:java_proto_library.bzl", "java_proto_library")
load("@protobuf//bazel:proto_library.bzl", "proto_library")
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

licenses(["notice"])

Expand Down Expand Up @@ -45,3 +46,19 @@ java_library(
"//third_party/java/junit",
],
)

kt_jvm_library(
name = "IntellijAspectResource",
testonly = 1,
srcs = ["src/com/google/idea/blaze/aspect/IntellijAspectResource.kt"],
visibility = ["//aspect/testing:__subpackages__"],
deps = [
":intellij_aspect_test_fixture_java_proto",
"//aspect/testing:guava",
"//intellij_platform_sdk:jsr305",
"//proto:common_java_proto",
"//proto:intellij_ide_info_java_proto",
"//third_party/java/junit",
"@maven//:org_jetbrains_kotlin_kotlin_stdlib",
],
)
63 changes: 47 additions & 16 deletions aspect/testing/rules/intellij_aspect_test_fixture.bzl
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
"""Rules for writing tests for the IntelliJ aspect."""

load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
load(
"//aspect:intellij_info.bzl",
"intellij_info_aspect",
)
load(
"//aspect:intellij_info_impl.bzl",
"IntelliJInfo",
"update_set_in_dict",
)
load("@rules_java//java:defs.bzl", "java_test")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("//aspect:intellij_info.bzl", "intellij_info_aspect")
load("//aspect:intellij_info_impl.bzl", "IntelliJInfo", "update_set_in_dict")

def _impl(ctx):
"""Implementation method for _intellij_aspect_test_fixture."""
Expand Down Expand Up @@ -67,6 +62,10 @@ _intellij_aspect_test_fixture = rule(
)

def intellij_aspect_test_fixture(name, deps, transitive_configs = []):
"""
Runs the aspect on `deps` and writes the output to a file.
"""

_intellij_aspect_test_fixture(
name = name,
output = name + ".intellij-aspect-test-fixture",
Expand All @@ -75,10 +74,42 @@ def intellij_aspect_test_fixture(name, deps, transitive_configs = []):
transitive_configs = transitive_configs,
)

def test_sources(outs):
for out in outs:
copy_file(
name = out + ".genrule",
src = out + ".testdata",
out = out,
)
def intellij_aspect_test(name, aspect_deps, **kwargs):
"""
Creates an intellij aspect test. Runs the aspect on `aspect_deps` and makes
the result available as a fixture called <name>_fixture. The fixture can be
loaded in the test using the IntellijAspectResource:

@Rule
@JvmField
val aspect: IntellijAspectResource = IntellijAspectResource(this::class.java)
"""

deps = list(kwargs.pop("deps", []))
deps.extend([
"//aspect/testing/rules:IntellijAspectResource",
"//intellij_platform_sdk:test_libs",
"//proto:common_java_proto",
"//third_party/java/junit",
])

data = list(kwargs.pop("data", []))
data.append(name + "_fixture")

intellij_aspect_test_fixture(
name = name + "_fixture",
deps = aspect_deps,
)

kt_jvm_library(
name = name + "_lib",
testonly = 1,
data = data,
deps = deps,
**kwargs
)

java_test(
name = name,
runtime_deps = [name + "_lib"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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.aspect

import com.google.common.base.Splitter
import com.google.devtools.intellij.IntellijAspectTestFixtureOuterClass.IntellijAspectTestFixture
import com.google.devtools.intellij.ideinfo.IntellijIdeInfo.CIdeInfo
import com.google.devtools.intellij.ideinfo.IntellijIdeInfo.TargetIdeInfo
import com.google.devtools.intellij.ideinfo.IntellijIdeInfo.TargetKey
import org.junit.rules.ExternalResource
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.nio.file.Path
import java.nio.file.Paths

/**
* JUnit resource for loading and accessing intellij aspect test fixtures.
* Prefer using this as JUnit rule over extending `BazelIntellijAspectTest`
* directly.
*/
class IntellijAspectResource (private val testCtx: Class<*>, private val fixtureLabel: String) : ExternalResource() {

/**
* Derives the fixture name from the context class. Use this constructor when
* using the `intellij_aspect_test` bazel macro.
*/
constructor(ctx: Class<*>) : this(ctx, ":${ctx.simpleName}_fixture")

private lateinit var fixture: IntellijAspectTestFixture

override fun before() {
fixture = loadAspectFixture(testCtx, fixtureLabel)
}

fun findTargets(
label: String,
externalRepo: String? = null,
fractionalAspectIds: List<String> = emptyList(),
): List<TargetIdeInfo> {
return fixture.targetsList.filter { matchTarget(it, testCtx, label, externalRepo, fractionalAspectIds) }
}

fun findTarget(
label: String,
externalRepo: String? = null,
fractionalAspectIds: List<String> = emptyList(),
): TargetIdeInfo {
return requireNotNull(findTargets(label, externalRepo, fractionalAspectIds).firstOrNull()) {
"target not found: $label"
}
}

fun findCIdeInfo(
label: String,
externalRepo: String? = null,
fractionalAspectIds: List<String> = emptyList(),
): CIdeInfo {
val target = findTarget(label, externalRepo, fractionalAspectIds)
require(target.hasCIdeInfo()) { "target has no c_ide_info: $label" }

return target.cIdeInfo
}
}

@Throws(IOException::class)
private fun loadAspectFixture(ctx: Class<*>, relativeLabel: String): IntellijAspectTestFixture {
val label = testRelativeLabel(ctx, relativeLabel)
val relativePath = (label.replace(':', '/') + ".intellij-aspect-test-fixture").substring(2)
val runfilesPath = runfilesPath(relativePath).toFile()

FileInputStream(runfilesPath).use { inputStream ->
return IntellijAspectTestFixture.parseFrom(inputStream)
}
}

/**
* Converts a relative label to a test relative label. The package of the
* context class is used as the base path.
*/
private fun testRelativeLabel(ctx: Class<*>, relativeLabel: String): String {
require(relativeLabel.startsWith(':'))

val basePath = Splitter.on("/com/").splitToList(System.getenv("TEST_BINARY"))[0]
val packagePath = Paths.get(basePath, ctx.getPackage().name.replace(".", File.separator));

return "//$packagePath$relativeLabel"
}

private fun runfilesPath(relativePath: String?): Path {
return Paths.get(getUserValue("TEST_SRCDIR"), getUserValue("TEST_WORKSPACE"), relativePath)
}

private fun getUserValue(name: String): String {
return requireNotNull(System.getProperty(name) ?: return System.getenv(name)) {
"$name environment variable or property not found"
}
}

/**
* Matches a target key, see [matchLabel] and [matchAspectIds] for details.
*/
private fun matchTarget(
info: TargetIdeInfo,
ctx: Class<*>,
label: String,
externalRepo: String?,
fractionalAspectIds: List<String>,
): Boolean {
return info.hasKey()
&& matchLabel(info.key, ctx, label, externalRepo)
&& matchAspectIds(info.key, fractionalAspectIds)
}

/**
* Matches target key against a label. If the label is relative it is treated
* as a test relative label. If a external repo is specified the label must be
* absolute with regard to that repo.
*/
private fun matchLabel(key: TargetKey, ctx: Class<*>, label: String, externalRepo: String?): Boolean {
if (externalRepo != null) {
require(label.startsWith("//")) { "external repo label must be absolute: $label" }
return key.label.endsWith("$externalRepo$label")
}
if (label.startsWith(':')) {
return key.label == testRelativeLabel(ctx, label)
}

return key.label == label
}

/**
* Matches a target key against a list of partial target keys. Returns true if
* any of the partial keys match or the list is empty.
*/
private fun matchAspectIds(key: TargetKey, fractionalAspectIds: List<String>): Boolean {
if (fractionalAspectIds.isEmpty()) return true

for (aspectId in key.aspectIdsList) {
if (key.aspectIdsList.any { it.contains(aspectId) }) return true
}

return false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("@rules_java//java:defs.bzl", "java_test")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test")

intellij_aspect_test(
name = "ForeignCcTest",
srcs = ["ForeignCcTest.kt"],
aspect_deps = ["@clwb_virtual_includes_project//main"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.aspect.cpp.foreigncc

import com.google.common.truth.Truth.assertThat
import com.google.idea.blaze.aspect.IntellijAspectResource
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

private const val REPOSITORY_NAME = "clwb_virtual_includes_project"

@RunWith(JUnit4::class)
class ForeignCcTest {

@Rule
@JvmField
val aspect: IntellijAspectResource = IntellijAspectResource(this::class.java)

@Test
fun testIncludePaths() {
val compilationCtx = aspect.findCIdeInfo("//main:main", REPOSITORY_NAME).compilationContext
assertThat(compilationCtx.systemIncludesList.filter { it.endsWith("lib/cmake/lib/include")}).hasSize(1)
}

/**
* Rules foreign_cc add the include path to the header files instead of the
* individual header files themselves. We probably need proper support for
* rules foreign_cc in the aspect to handle this.
*/
@Test
fun testHeadersContainIncludePath() {
val compilationCtx = aspect.findCIdeInfo("//main:main", REPOSITORY_NAME).compilationContext
assertThat(compilationCtx.headersList.filter { it.relativePath == "lib/cmake/lib/include" }).hasSize(1)
}
}
Loading