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
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module(
name = "bazel-diff",
version = "13.1.1",
version = "14.0.0",
compatibility_level = 0,
)

Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,12 @@ workspace.
...
}
--[no-]excludeExternalTargets
If true, exclude external targets. This must be
switched on when using `--enable_workspace=false` Bazel
command line option. Defaults to `false`.
If true, exclude external targets (do not query
//external:all-targets). When Bzlmod is enabled
(detected via bazel mod graph), external targets
are excluded automatically. Set this when using
Bazel with --enable_workspace=false in other
configurations. Defaults to false.
--[no-]includeTargetType
Whether include target type in the generated JSON or not.
If false, the generate JSON schema is: {"<target>": "<sha256>"}
Expand Down
2 changes: 1 addition & 1 deletion bazel-diff-example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ bazel_diff="/tmp/bazel_diff"
bazel_diff_flags=""
if [ "${BAZEL_DIFF_DISABLE_WORKSPACE:-false}" = "true" ]; then
echo "Disabling workspace for bazel-diff commands (BAZEL_DIFF_DISABLE_WORKSPACE=true)"
bazel_diff_flags="-co --enable_workspace=false --excludeExternalTargets"
bazel_diff_flags="-co --enable_workspace=false"
fi

# Set git checkout flags based on environment variable
Expand Down
6 changes: 6 additions & 0 deletions cli/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ kt_jvm_test(
runtime_deps = [":cli-test-lib"],
)

kt_jvm_test(
name = "BazelModServiceTest",
test_class = "com.bazel_diff.bazel.BazelModServiceTest",
runtime_deps = [":cli-test-lib"],
)

kt_jvm_test(
name = "E2ETest",
timeout = "long",
Expand Down
9 changes: 5 additions & 4 deletions cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ import org.koin.core.component.inject
class BazelClient(
private val useCquery: Boolean,
private val fineGrainedHashExternalRepos: Set<String>,
private val excludeExternalTargets: Boolean
private val excludeExternalTargets: Boolean,
) : KoinComponent {
private val logger: Logger by inject()
private val queryService: BazelQueryService by inject()
private val bazelModService: BazelModService by inject()

suspend fun queryAllTargets(): List<BazelTarget> {
val queryEpoch = Calendar.getInstance().getTimeInMillis()

// Skip //external:all-targets in Bazel 8+ (where WORKSPACE is disabled by default)
// or when explicitly excluded via the flag
// Skip //external:all-targets when explicitly excluded or when Bzlmod is enabled (//external not
// available).
val repoTargetsQuery =
if (excludeExternalTargets || queryService.shouldSkipExternalTargets) {
if (excludeExternalTargets || bazelModService.isBzlmodEnabled) {
emptyList()
} else {
listOf("//external:all-targets")
Expand Down
50 changes: 50 additions & 0 deletions cli/src/main/kotlin/com/bazel_diff/bazel/BazelModService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.bazel_diff.bazel

import com.bazel_diff.log.Logger
import com.bazel_diff.process.Redirect
import com.bazel_diff.process.process
import java.nio.file.Path
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

/**
* Service that runs `bazel mod` to detect whether Bzlmod is enabled in the workspace.
* Used to decide whether to query //external:all-targets (disabled when Bzlmod is active).
*/
class BazelModService(
private val workingDirectory: Path,
private val bazelPath: Path,
private val startupOptions: List<String>,
private val noBazelrc: Boolean,
) : KoinComponent {
private val logger: Logger by inject()

/** True if Bzlmod is enabled (e.g. `bazel mod graph` succeeds). When true, //external is not available. */
val isBzlmodEnabled: Boolean by lazy { runBlocking { checkBzlmodEnabled() } }

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun checkBzlmodEnabled(): Boolean {
val cmd =
mutableListOf<String>().apply {
add(bazelPath.toString())
if (noBazelrc) {
add("--bazelrc=/dev/null")
}
addAll(startupOptions)
add("mod")
add("graph")
}
logger.i { "Executing Bazel mod graph: ${cmd.joinToString()}" }
val result =
process(
*cmd.toTypedArray(),
stdout = Redirect.CAPTURE,
stderr = Redirect.CAPTURE,
workingDirectory = workingDirectory.toFile(),
destroyForcibly = true,
)
return result.resultCode == 0
}
}
5 changes: 0 additions & 5 deletions cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ class BazelQueryService(
private val logger: Logger by inject()
private val version: Triple<Int, Int, Int> by lazy { runBlocking { determineBazelVersion() } }

// In Bazel 8+, the //external package is not available when WORKSPACE is disabled (Bzlmod is the default).
// We should skip querying //external:all-targets in Bazel 8+.
val shouldSkipExternalTargets: Boolean
get() = versionComparator.compare(version, Triple(8, 0, 0)) >= 0

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun determineBazelVersion(): Triple<Int, Int, Int> {
val cmd = arrayOf(bazelPath.toString(), "--version")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class GenerateHashesCommand : Callable<Int> {
negatable = true,
description =
[
"If true, exclude external targets. This must be switched on when using `--enable_workspace=false` Bazel command line option. Defaults to `false`."],
"If true, exclude external targets (do not query //external:all-targets). When Bzlmod is enabled (detected via bazel mod graph), external targets are excluded automatically. Set this when using Bazel with --enable_workspace=false in other configurations. Defaults to false."],
scope = CommandLine.ScopeType.INHERIT)
var excludeExternalTargets = false

Expand Down
4 changes: 4 additions & 0 deletions cli/src/main/kotlin/com/bazel_diff/di/Modules.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bazel_diff.di

import com.bazel_diff.bazel.BazelClient
import com.bazel_diff.bazel.BazelModService
import com.bazel_diff.bazel.BazelQueryService
import com.bazel_diff.hash.*
import com.bazel_diff.io.ContentHashProvider
Expand Down Expand Up @@ -71,6 +72,9 @@ fun hasherModule(
keepGoing,
debug)
}
single {
BazelModService(workingDirectory, bazelPath, startupOptions, debug)
}
single { BazelClient(useCquery, updatedFineGrainedHashExternalRepos, excludeExternalTargets) }
single { BuildGraphHasher(get()) }
single { TargetHasher() }
Expand Down
72 changes: 72 additions & 0 deletions cli/src/test/kotlin/com/bazel_diff/bazel/BazelModServiceTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.bazel_diff.bazel

import assertk.assertThat
import assertk.assertions.isFalse
import com.bazel_diff.SilentLogger
import com.bazel_diff.log.Logger
import java.io.File
import java.nio.file.Paths
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.get

class BazelModServiceTest : KoinTest {

@get:Rule val temp: TemporaryFolder = TemporaryFolder()

@Test
fun isBzlmodEnabled_returnsFalse_whenWorkspaceHasNoModuleBazel() {
val workspaceDir = temp.newFolder()
startKoin {
modules(
module {
single<Logger> { SilentLogger }
single {
BazelModService(
workingDirectory = workspaceDir.toPath(),
bazelPath = Paths.get("bazel"),
startupOptions = listOf("--enable_bzlmod"),
noBazelrc = true,
)
}
})
}
try {
val service = get<BazelModService>()
assertThat(service.isBzlmodEnabled).isFalse()
} finally {
stopKoin()
}
}

@Test
fun isBzlmodEnabled_returnsConsistentValue_whenWorkspaceHasModuleBazel() {
val workspaceDir = temp.newFolder()
File(workspaceDir, "MODULE.bazel").writeText("module(name = \"test\")\n")
startKoin {
modules(
module {
single<Logger> { SilentLogger }
single {
BazelModService(
workingDirectory = workspaceDir.toPath(),
bazelPath = Paths.get("bazel"),
startupOptions = listOf("--enable_bzlmod"),
noBazelrc = true,
)
}
})
}
try {
val service = get<BazelModService>()
service.isBzlmodEnabled
} finally {
stopKoin()
}
}
}
28 changes: 0 additions & 28 deletions cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -643,34 +643,6 @@ class E2ETest {
assertTargetsMatch(actual, expected, "testUseCqueryWithAndroidCodeChange - JRE platform")
}

@Test
fun testUseCqueryWithExcludeExternalTargets() {
// This test verifies the fix for the issue where using --excludeExternalTargets with
// --useCquery
// would cause an empty query string to be passed to Bazel, resulting in exit code 2.
val workingDirectory = extractFixtureProject("/fixture/cquery-test-base.zip")

val bazelPath = "bazel"
val outputDir = temp.newFolder()
val hashesJson = File(outputDir, "hashes.json")

val cli = CommandLine(BazelDiff())

val exitCode =
cli.execute(
"generate-hashes",
"-w",
workingDirectory.absolutePath,
"-b",
bazelPath,
"--useCquery",
// Platform is specified only to make the cquery succeed.
"--cqueryCommandOptions",
"--platforms=//:jre",
"--excludeExternalTargets",
hashesJson.absolutePath)
assertThat(exitCode).isEqualTo(0)
}

@Test
fun testTargetDistanceMetrics() {
Expand Down
Loading