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 = "12.1.0",
version = "12.1.1",
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.

4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ release_deploy_jar:
build \
//cli:bazel-diff_deploy.jar \
-c opt

.PHONY: format
format:
bazelisk run //cli/format
8 changes: 1 addition & 7 deletions cli/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")
load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test")

Expand Down Expand Up @@ -143,10 +142,5 @@ java_binary(
name = "ktfmt",
main_class = "com.facebook.ktfmt.cli.Main",
runtime_deps = ["@ktfmt//jar"],
)

format_multirun(
name = "format",
kotlin = ":ktfmt",
visibility = ["//visibility:public"],
visibility = ["//cli/format:__pkg__"],
)
7 changes: 7 additions & 0 deletions cli/format/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")

format_multirun(
name = "format",
kotlin = "//cli:ktfmt",
visibility = ["//visibility:public"],
)
11 changes: 6 additions & 5 deletions cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ class BazelClient(
// show up in
// `configuredRuleInput`. Hence, one must not filter them out with `kind(rule, deps(..))`.
val mainTargets = queryService.query("deps(//...:all-targets)", useCquery = true)
val repoTargets = if (repoTargetsQuery.isNotEmpty()) {
queryService.query(repoTargetsQuery.joinToString(" + ") { "'$it'" })
} else {
emptyList()
}
val repoTargets =
if (repoTargetsQuery.isNotEmpty()) {
queryService.query(repoTargetsQuery.joinToString(" + ") { "'$it'" })
} else {
emptyList()
}
(mainTargets + repoTargets).distinctBy { it.name }
} else {
val buildTargetsQuery =
Expand Down
82 changes: 42 additions & 40 deletions cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

private val versionComparator = compareBy<Triple<Int, Int, Int>> { it.first }
.thenBy { it.second }
.thenBy { it.third }
private val versionComparator =
compareBy<Triple<Int, Int, Int>> { it.first }.thenBy { it.second }.thenBy { it.third }

class BazelQueryService(
private val workingDirectory: Path,
Expand All @@ -27,27 +26,26 @@ class BazelQueryService(
private val noBazelrc: Boolean,
) : KoinComponent {
private val logger: Logger by inject()
private val version: Triple<Int, Int, Int> by lazy {
runBlocking { determineBazelVersion() }
}
private val version: Triple<Int, Int, Int> by lazy { runBlocking { determineBazelVersion() } }

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun determineBazelVersion(): Triple<Int, Int, Int> {
val cmd = arrayOf(bazelPath.toString(), "--version")
logger.i { "Executing Bazel version command: ${cmd.joinToString()}" }
val result = process(
*cmd,
stdout = Redirect.CAPTURE,
workingDirectory = workingDirectory.toFile(),
stderr = Redirect.PRINT,
destroyForcibly = true,
)
val result =
process(
*cmd,
stdout = Redirect.CAPTURE,
workingDirectory = workingDirectory.toFile(),
stderr = Redirect.PRINT,
destroyForcibly = true,
)

if (result.resultCode != 0) {
throw RuntimeException("Bazel version command failed, exit code ${result.resultCode}")
}

if (result.output.size != 1 || !result.output.first().startsWith("bazel ")) {
if (result.output.size != 1 || !result.output.first().startsWith("bazel ")) {
throw RuntimeException("Bazel version command returned unexpected output: ${result.output}")
}
// Trim off any prerelease suffixes.
Expand All @@ -56,12 +54,14 @@ class BazelQueryService(
return Triple(version[0], version[1], version[2])
}

// Use streamed_proto output for cquery if available. This is more efficient than the proto output.
// Use streamed_proto output for cquery if available. This is more efficient than the proto
// output.
// https://github.com/bazelbuild/bazel/commit/607d0f7335f95aa0ee236ba3c18ce2a232370cdb
private val canUseStreamedProtoWithCquery
get() = versionComparator.compare(version, Triple(7, 0, 0)) >= 0

// Use an output file for (c)query if supported. This avoids excessively large stdout, which is sent out on the BES.
// Use an output file for (c)query if supported. This avoids excessively large stdout, which is
// sent out on the BES.
// https://github.com/bazelbuild/bazel/commit/514e9052f2c603c53126fbd9436bdd3ad3a1b0c7
private val canUseOutputFile
get() = versionComparator.compare(version, Triple(8, 2, 0)) >= 0
Expand All @@ -87,20 +87,21 @@ class BazelQueryService(
outputFile.inputStream().buffered().use { proto ->
if (useCquery) {
if (canUseStreamedProtoWithCquery) {
mutableListOf<AnalysisProtosV2.CqueryResult>()
.apply {
while (true) {
val result = AnalysisProtosV2.CqueryResult.parseDelimitedFrom(proto) ?: break
// EOF
add(result)
}
mutableListOf<AnalysisProtosV2.CqueryResult>()
.apply {
while (true) {
val result =
AnalysisProtosV2.CqueryResult.parseDelimitedFrom(proto) ?: break
// EOF
add(result)
}
}
.flatMap { it.resultsList }
} else {
AnalysisProtosV2.CqueryResult.parseFrom(proto).resultsList
}
.flatMap { it.resultsList }
} else {
AnalysisProtosV2.CqueryResult.parseFrom(proto).resultsList
}
.mapNotNull { toBazelTarget(it.target) }
.filter { it.name in compatibleTargetSet }
.mapNotNull { toBazelTarget(it.target) }
.filter { it.name in compatibleTargetSet }
} else {
mutableListOf<Build.Target>()
.apply {
Expand Down Expand Up @@ -167,8 +168,7 @@ class BazelQueryService(
return str(target.label)
return ""
"""
.trimIndent()
)
.trimIndent())
add(cqueryStarlarkFile.toString())
} else {
add(if (canUseStreamedProtoWithCquery) "streamed_proto" else "proto")
Expand Down Expand Up @@ -199,17 +199,19 @@ class BazelQueryService(

logger.i { "Executing Query: $query" }
logger.i { "Command: ${cmd.toTypedArray().joinToString()}" }
val result = process(
*cmd.toTypedArray(),
stdout = if (canUseOutputFile) Redirect.SILENT else Redirect.ToFile(outputFile),
workingDirectory = workingDirectory.toFile(),
stderr = Redirect.PRINT,
destroyForcibly = true,
)
val result =
process(
*cmd.toTypedArray(),
stdout = if (canUseOutputFile) Redirect.SILENT else Redirect.ToFile(outputFile),
workingDirectory = workingDirectory.toFile(),
stderr = Redirect.PRINT,
destroyForcibly = true,
)

if (!allowedExitCodes.contains(result.resultCode)) {
logger.w { "Bazel query failed, output: ${result.output.joinToString("\n")}" }
throw RuntimeException("Bazel query failed, exit code ${result.resultCode}, allowed exit codes: ${allowedExitCodes.joinToString()}")
logger.w { "Bazel query failed, output: ${result.output.joinToString("\n")}" }
throw RuntimeException(
"Bazel query failed, exit code ${result.resultCode}, allowed exit codes: ${allowedExitCodes.joinToString()}")
}
return outputFile
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ class GenerateHashesCommand : Callable<Int> {
var fineGrainedHashExternalRepos: Set<String> = emptySet()

@CommandLine.Option(
names = ["--fineGrainedHashExternalReposFile"],
description =
[
"A text file containing a newline separated list of external repos. Similar to --fineGrainedHashExternalRepos but helps you avoid exceeding max arg length. Mutually exclusive with --fineGrainedHashExternalRepos."])
names = ["--fineGrainedHashExternalReposFile"],
description =
[
"A text file containing a newline separated list of external repos. Similar to --fineGrainedHashExternalRepos but helps you avoid exceeding max arg length. Mutually exclusive with --fineGrainedHashExternalRepos."])
var fineGrainedHashExternalReposFile: File? = null

@CommandLine.Option(
Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/kotlin/com/bazel_diff/cli/VersionProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class VersionProvider : IVersionProvider {
val inputStream =
classLoader.getResourceAsStream("cli/version")
?: classLoader.getResourceAsStream("version")
?: throw IllegalArgumentException(
?: throw IllegalArgumentException(
"unknown version as version file not found in resources")

val version = BufferedReader(InputStreamReader(inputStream)).use { it.readText().trim() }
Expand Down
12 changes: 6 additions & 6 deletions cli/src/main/kotlin/com/bazel_diff/di/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ fun hasherModule(
excludeExternalTargets: Boolean,
): Module = module {
if (fineGrainedHashExternalReposFile != null && fineGrainedHashExternalRepos.isNotEmpty()) {
System.err.println("Error: fineGrainedHashExternalReposFile and fineGrainedHashExternalRepos are mutually exclusive - please provide only one of them")
System.err.println(
"Error: fineGrainedHashExternalReposFile and fineGrainedHashExternalRepos are mutually exclusive - please provide only one of them")
System.exit(1)
}
val updatedFineGrainedHashExternalRepos = fineGrainedHashExternalReposFile?.let { file ->
file.readLines()
.filter { it.isNotBlank() }
.toSet()
} ?: fineGrainedHashExternalRepos
val updatedFineGrainedHashExternalRepos =
fineGrainedHashExternalReposFile?.let { file ->
file.readLines().filter { it.isNotBlank() }.toSet()
} ?: fineGrainedHashExternalRepos

val cmd: MutableList<String> =
ArrayList<String>().apply {
Expand Down
28 changes: 15 additions & 13 deletions cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ class E2ETest {

@Test
fun testUseCqueryWithExcludeExternalTargets() {
// This test verifies the fix for the issue where using --excludeExternalTargets with --useCquery
// 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")

Expand All @@ -599,18 +600,19 @@ class E2ETest {

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)
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)
}

Expand Down
57 changes: 30 additions & 27 deletions cli/src/test/kotlin/com/bazel_diff/hash/SourceFileHasherTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -216,31 +216,34 @@ internal class SourceFileHasherTest : KoinTest {
}

@Test
fun testHashEmptyFileVsDeletedFile() = runBlocking<Unit> {
// Create a temp directory for testing
val testDir = Files.createTempDirectory("empty_file_test")
val emptyFilePath = testDir.resolve("path/to/empty.txt")
Files.createDirectories(emptyFilePath.parent)
Files.createFile(emptyFilePath)

val emptyFileTarget = "//path/to:empty.txt"
val hasher = SourceFileHasherImpl(testDir, null, externalRepoResolver)

// Hash the empty file (file exists)
val emptyFileHash = hasher.digest(BazelSourceFileTarget(emptyFileTarget, seed)).toHexString()

// Delete the file
Files.delete(emptyFilePath)

// Hash the non-existent file
val deletedFileHash = hasher.digest(BazelSourceFileTarget(emptyFileTarget, seed)).toHexString()

// The hashes should be different (file exists vs file missing)
assertThat(emptyFileHash).isNotEqualTo(deletedFileHash)

// Clean up
Files.deleteIfExists(emptyFilePath.parent)
Files.deleteIfExists(testDir.resolve("path"))
Files.deleteIfExists(testDir)
}
fun testHashEmptyFileVsDeletedFile() =
runBlocking<Unit> {
// Create a temp directory for testing
val testDir = Files.createTempDirectory("empty_file_test")
val emptyFilePath = testDir.resolve("path/to/empty.txt")
Files.createDirectories(emptyFilePath.parent)
Files.createFile(emptyFilePath)

val emptyFileTarget = "//path/to:empty.txt"
val hasher = SourceFileHasherImpl(testDir, null, externalRepoResolver)

// Hash the empty file (file exists)
val emptyFileHash =
hasher.digest(BazelSourceFileTarget(emptyFileTarget, seed)).toHexString()

// Delete the file
Files.delete(emptyFilePath)

// Hash the non-existent file
val deletedFileHash =
hasher.digest(BazelSourceFileTarget(emptyFileTarget, seed)).toHexString()

// The hashes should be different (file exists vs file missing)
assertThat(emptyFileHash).isNotEqualTo(deletedFileHash)

// Clean up
Files.deleteIfExists(emptyFilePath.parent)
Files.deleteIfExists(testDir.resolve("path"))
Files.deleteIfExists(testDir)
}
}
Loading