diff --git a/README.md b/README.md index 4c638e3..fd6db4a 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,14 @@ Commands: ### `generate-hashes` command ```terminal -Usage: bazel-diff generate-hashes [-hkvV] -b= [-s=] - -w= +Usage: bazel-diff generate-hashes [-hkvV] [--[no-]useCquery] [-b=] + [--contentHashPath=] + [-s=] -w= [-co=]... + [--cqueryCommandOptions= + ]... + [--fineGrainedHashExternalRepos=]... [-so=]... Writes to a file the SHA256 hashes for each Bazel Target in the provided workspace. @@ -95,12 +100,16 @@ workspace. binary available in PATH will be used. -co, --bazelCommandOptions= Additional space separated Bazel command options used - when invoking Bazel + when invoking `bazel query` --contentHashPath= Path to content hash json file. It's a map which maps relative file path from workspace path to its content hash. Files in this map will skip content hashing and use provided value + --cqueryCommandOptions= + Additional space separated Bazel command options used + when invoking `bazel cquery`. This flag is has no + effect if `--useCquery`is false. --fineGrainedHashExternalRepos= Comma separate list of external repos in which fine-grained hashes are computed for the targets. @@ -124,12 +133,24 @@ workspace. -so, --bazelStartupOptions= Additional space separated Bazel client startup options used when invoking Bazel + --[no-]useCquery If true, use cquery instead of query when generating + dependency graphs. Using cquery would yield more + accurate build graph at the cost of slower query + execution. When this is set, one usually also wants + to set `--cqueryCommandOptions` to specify a + targeting platform. Note that this flag only works + with Bazel 6.2.0 or above because lower versions + does not support `--query_file` flag. -v, --verbose Display query string, missing files and elapsed time -V, --version Print version information and exit. -w, --workspacePath= Path to Bazel workspace directory. ``` +**Note**: `--useCquery` flag may not work with very large repos due to limitation +of Bazel. You may want to fallback to use normal query mode in that case. +See https://github.com/bazelbuild/bazel/issues/17743 for more details. + ### What does the SHA256 value of `generate-hashes` represent? `generate-hashes` is a canonical SHA256 value representing all attributes and inputs into a target. These inputs diff --git a/cli/BUILD b/cli/BUILD index 47d7477..7556a31 100644 --- a/cli/BUILD +++ b/cli/BUILD @@ -25,34 +25,18 @@ kt_jvm_library( name = "cli-lib", srcs = glob(["src/main/kotlin/**/*.kt"]), deps = [ - ":build_java_proto", "@bazel_diff_maven//:com_google_code_gson_gson", "@bazel_diff_maven//:com_google_guava_guava", "@bazel_diff_maven//:info_picocli_picocli", "@bazel_diff_maven//:io_insert_koin_koin_core_jvm", "@bazel_diff_maven//:org_apache_commons_commons_pool2", "@bazel_diff_maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm", + "@bazel_tools//src/main/protobuf:analysis_v2_java_proto", + "@bazel_tools//src/main/protobuf:build_java_proto", "@com_google_protobuf//:protobuf_java", ], ) -java_proto_library( - name = "build_java_proto", - deps = [":build_proto"], -) - -proto_library( - name = "build_proto", - srcs = [":build_proto_gen"], -) - -genrule( - name = "build_proto_gen", - srcs = ["@bazel_tools//src/main/protobuf:build.proto"], - outs = ["build.proto"], - cmd = "cp $< $@", -) - kt_jvm_test( name = "BuildGraphHasherTest", test_class = "com.bazel_diff.hash.BuildGraphHasherTest", diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt index 9ff4981..dd91243 100644 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt +++ b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelClient.kt @@ -6,15 +6,33 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* -class BazelClient(private val fineGrainedHashExternalRepos: Set) : KoinComponent { +class BazelClient(private val useCquery: Boolean, private val fineGrainedHashExternalRepos: Set) : KoinComponent { private val logger: Logger by inject() private val queryService: BazelQueryService by inject() suspend fun queryAllTargets(): List { val queryEpoch = Calendar.getInstance().getTimeInMillis() - val query = listOf("//external:all-targets", "//...:all-targets") + fineGrainedHashExternalRepos.map { "@$it//...:all-targets" } - val targets = queryService.query(query.joinToString(" + ") { "'$it'" }) + val repoTargetsQuery = listOf("//external:all-targets") + val targets = if (useCquery) { + // Explicitly listing external repos here sometimes causes issues mentioned at + // https://bazel.build/query/cquery#recursive-target-patterns. Hence, we query all dependencies with `deps` + // instead. However, we still need to append all "//external:*" targets because fine-grained hash + // computation depends on hashing of source files in external repos as well, which is limited to repos + // explicitly mentioned in `fineGrainedHashExternalRepos` flag. Therefore, for any repos not mentioned there + // we are still relying on the repo-generation target under `//external` to compute the hash. + // + // In addition, we must include all source dependencies in this query in order for them to show up in + // `configuredRuleInput`. Hence, one must not filter them out with `kind(rule, deps(..))`. However, these + // source targets are omitted inside BazelQueryService with the custom starlark function used to print + // labels. + (queryService.query("deps(//...:all-targets)", useCquery = true) + + queryService.query(repoTargetsQuery.joinToString(" + ") { "'$it'" })) + .distinctBy { it.rule.name } + } else { + val buildTargetsQuery = listOf("//...:all-targets") + fineGrainedHashExternalRepos.map { "@$it//...:all-targets" } + queryService.query((repoTargetsQuery + buildTargetsQuery).joinToString(" + ") { "'$it'" }) + } val queryDuration = Calendar.getInstance().getTimeInMillis() - queryEpoch logger.i { "All targets queried in $queryDuration" } return targets.mapNotNull { target: Build.Target -> diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt index 8cc0ae0..a43d519 100644 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt +++ b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelQueryService.kt @@ -3,12 +3,13 @@ package com.bazel_diff.bazel import com.bazel_diff.log.Logger import com.bazel_diff.process.Redirect import com.bazel_diff.process.process +import com.google.devtools.build.lib.analysis.AnalysisProtosV2 import com.google.devtools.build.lib.query2.proto.proto2api.Build import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.nio.charset.StandardCharsets +import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -17,16 +18,53 @@ class BazelQueryService( private val bazelPath: Path, private val startupOptions: List, private val commandOptions: List, - private val keepGoing: Boolean?, + private val cqueryOptions: List, + private val keepGoing: Boolean, private val noBazelrc: Boolean, ) : KoinComponent { private val logger: Logger by inject() + suspend fun query(query: String, useCquery: Boolean = false): List { + // Unfortunately, there is still no direct way to tell if a target is compatible or not with the proto output + // by itself. So we do an extra cquery with the trick at + // https://bazel.build/extending/platforms#cquery-incompatible-target-detection to first find all compatible + // targets. + val compatibleTargetSet = + if (useCquery) { + runQuery(query, useCquery = true, outputCompatibleTargets = true).useLines { + it.filter { it.isNotBlank() }.toSet() + } + } else { + emptySet() + } + val outputFile = runQuery(query, useCquery) + + val targets = outputFile.inputStream().buffered().use { proto -> + if (useCquery) { + val cqueryResult = AnalysisProtosV2.CqueryResult.parseFrom(proto) + cqueryResult.resultsList.filter { it.target.rule.name in compatibleTargetSet }.map { it.target } + } else { + mutableListOf().apply { + while (true) { + val target = Build.Target.parseDelimitedFrom(proto) ?: break + // EOF + add(target) + } + } + } + } + + return targets + } + @OptIn(ExperimentalCoroutinesApi::class) - suspend fun query(query: String): List { - val tempFile = Files.createTempFile(null, ".txt") - val outputFile = Files.createTempFile(null, ".bin") - Files.write(tempFile, query.toByteArray(StandardCharsets.UTF_8)) + private suspend fun runQuery(query: String, useCquery: Boolean, outputCompatibleTargets: Boolean = false): File { + val queryFile = Files.createTempFile(null, ".txt").toFile() + queryFile.deleteOnExit() + val outputFile = Files.createTempFile(null, ".bin").toFile() + outputFile.deleteOnExit() + + queryFile.writeText(query) logger.i { "Executing Query: $query" } val cmd: MutableList = ArrayList().apply { @@ -35,41 +73,69 @@ class BazelQueryService( add("--bazelrc=/dev/null") } addAll(startupOptions) - add("query") + if (useCquery) { + add("cquery") + add("--transitions=lite") + } else { + add("query") + } add("--output") - add("streamed_proto") - add("--order_output=no") - if (keepGoing != null && keepGoing) { + if (useCquery) { + if (outputCompatibleTargets) { + add("starlark") + add("--starlark:file") + val cqueryOutputFile = Files.createTempFile(null, ".cquery").toFile() + cqueryOutputFile.deleteOnExit() + cqueryOutputFile.writeText(""" + def format(target): + if providers(target) == None: + # skip printing non-target results. That is, source files and generated files won't be + # printed + return "" + if "IncompatiblePlatformProvider" not in providers(target): + label = str(target.label) + if label.startswith("@//"): + # normalize label to be consistent with content inside proto + return label[1:] + else: + return label + return "" + """.trimIndent()) + add(cqueryOutputFile.toString()) + } else { + // Unfortunately, cquery does not support streamed_proto yet. + // See https://github.com/bazelbuild/bazel/issues/17743. This poses an issue for large monorepos. + add("proto") + } + } else { + add("streamed_proto") + } + if (!useCquery) { + add("--order_output=no") + } + if (keepGoing) { add("--keep_going") } - addAll(commandOptions) + if (useCquery) { + addAll(cqueryOptions) + } else { + addAll(commandOptions) + } add("--query_file") - add(tempFile.toString()) + add(queryFile.toString()) } val result = runBlocking { process( *cmd.toTypedArray(), - stdout = Redirect.ToFile(outputFile.toFile()), + stdout = Redirect.ToFile(outputFile), workingDirectory = workingDirectory.toFile(), stderr = Redirect.PRINT, destroyForcibly = true, ) } - if(result.resultCode != 0) throw RuntimeException("Bazel query failed, exit code ${result.resultCode}") - - val targets = mutableListOf() - outputFile.toFile().inputStream().buffered().use {stream -> - while (true) { - val target = Build.Target.parseDelimitedFrom(stream) ?: break - // EOF - targets.add(target) - } - } - - Files.delete(tempFile) - Files.delete(outputFile) - return targets + if (result.resultCode != 0) throw RuntimeException("Bazel query failed, exit code ${result.resultCode}") + return outputFile } } diff --git a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt index 6566c12..18b422e 100644 --- a/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt +++ b/cli/src/main/kotlin/com/bazel_diff/bazel/BazelRule.kt @@ -22,8 +22,16 @@ class BazelRule(private val rule: Build.Rule) { } } - fun ruleInputList(fineGrainedHashExternalRepos: Set): List { - return rule.ruleInputList.map { ruleInput: String -> transformRuleInput(fineGrainedHashExternalRepos, ruleInput) } + fun ruleInputList(useCquery: Boolean, fineGrainedHashExternalRepos: Set): List { + return if (useCquery) { + rule.configuredRuleInputList.map { it.label } + + rule.ruleInputList.map { ruleInput: String -> transformRuleInput(fineGrainedHashExternalRepos, ruleInput) } + // Only keep the non-fine-grained ones because the others are already covered by configuredRuleInputList + .filter { it.startsWith("//external:") } + .distinct() + } else { + rule.ruleInputList.map { ruleInput: String -> transformRuleInput(fineGrainedHashExternalRepos, ruleInput) } + } } val name: String = rule.name diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt index bd76628..f45f11e 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt @@ -59,7 +59,7 @@ class GenerateHashesCommand : Callable { @CommandLine.Option( names = ["-co", "--bazelCommandOptions"], - description = ["Additional space separated Bazel command options used when invoking Bazel"], + description = ["Additional space separated Bazel command options used when invoking `bazel query`"], scope = CommandLine.ScopeType.INHERIT, converter = [OptionsConverter::class], ) @@ -73,6 +73,22 @@ class GenerateHashesCommand : Callable { ) var fineGrainedHashExternalRepos: Set = emptySet() + @CommandLine.Option( + names = ["--useCquery"], + negatable = true, + description = ["If true, use cquery instead of query when generating dependency graphs. Using cquery would yield more accurate build graph at the cost of slower query execution. When this is set, one usually also wants to set `--cqueryCommandOptions` to specify a targeting platform. Note that this flag only works with Bazel 6.2.0 or above because lower versions does not support `--query_file` flag."], + scope = CommandLine.ScopeType.INHERIT + ) + var useCquery = false + + @CommandLine.Option( + names = ["--cqueryCommandOptions"], + description = ["Additional space separated Bazel command options used when invoking `bazel cquery`. This flag is has no effect if `--useCquery`is false."], + scope = CommandLine.ScopeType.INHERIT, + converter = [OptionsConverter::class], + ) + var cqueryCommandOptions: List = emptyList() + @CommandLine.Option( names = ["-k", "--keep_going"], negatable = true, @@ -108,6 +124,8 @@ class GenerateHashesCommand : Callable { contentHashPath, bazelStartupOptions, bazelCommandOptions, + cqueryCommandOptions, + useCquery, keepGoing, fineGrainedHashExternalRepos, ), diff --git a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt index f7d37b1..c9837fb 100644 --- a/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt +++ b/cli/src/main/kotlin/com/bazel_diff/di/Modules.kt @@ -28,7 +28,9 @@ fun hasherModule( contentHashPath: File?, startupOptions: List, commandOptions: List, - keepGoing: Boolean?, + cqueryOptions: List, + useCquery: Boolean, + keepGoing: Boolean, fineGrainedHashExternalRepos: Set, ): Module = module { val debug = System.getProperty("DEBUG", "false").equals("true") @@ -38,14 +40,15 @@ fun hasherModule( bazelPath, startupOptions, commandOptions, + cqueryOptions, keepGoing, debug ) } - single { BazelClient(fineGrainedHashExternalRepos) } + single { BazelClient(useCquery, fineGrainedHashExternalRepos) } single { BuildGraphHasher(get()) } single { TargetHasher() } - single { RuleHasher(fineGrainedHashExternalRepos) } + single { RuleHasher(useCquery, fineGrainedHashExternalRepos) } single { SourceFileHasher(fineGrainedHashExternalRepos) } single(named("working-directory")) { workingDirectory } single(named("output-base")) { diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt index 704dfe3..b13bba0 100644 --- a/cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt +++ b/cli/src/main/kotlin/com/bazel_diff/hash/RuleHasher.kt @@ -8,7 +8,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.concurrent.ConcurrentMap -class RuleHasher(private val fineGrainedHashExternalRepos: Set) : KoinComponent { +class RuleHasher(private val useCquery: Boolean, private val fineGrainedHashExternalRepos: Set) : KoinComponent { private val logger: Logger by inject() private val sourceFileHasher: SourceFileHasher by inject() @@ -43,7 +43,7 @@ class RuleHasher(private val fineGrainedHashExternalRepos: Set) : KoinCo safePutBytes(rule.digest) safePutBytes(seedHash) - for (ruleInput in rule.ruleInputList(fineGrainedHashExternalRepos)) { + for (ruleInput in rule.ruleInputList(useCquery, fineGrainedHashExternalRepos)) { safePutBytes(ruleInput.toByteArray()) val inputRule = allRulesMap[ruleInput] diff --git a/cli/src/test/kotlin/com/bazel_diff/Modules.kt b/cli/src/test/kotlin/com/bazel_diff/Modules.kt index 59443ae..05a534a 100644 --- a/cli/src/test/kotlin/com/bazel_diff/Modules.kt +++ b/cli/src/test/kotlin/com/bazel_diff/Modules.kt @@ -1,7 +1,6 @@ package com.bazel_diff import com.bazel_diff.bazel.BazelClient -import com.bazel_diff.bazel.BazelQueryService import com.bazel_diff.hash.BuildGraphHasher import com.bazel_diff.hash.RuleHasher import com.bazel_diff.hash.SourceFileHasher @@ -16,10 +15,10 @@ import java.nio.file.Paths fun testModule(): Module = module { single { SilentLogger } - single { BazelClient(emptySet()) } + single { BazelClient(false, emptySet()) } single { BuildGraphHasher(get()) } single { TargetHasher() } - single { RuleHasher(emptySet()) } + single { RuleHasher(false, emptySet()) } single { SourceFileHasher() } single { GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create() } single(named("working-directory")) { Paths.get("working-directory") } diff --git a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt index ea0e6cc..001779c 100644 --- a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt @@ -1,7 +1,6 @@ package com.bazel_diff.e2e import assertk.assertThat -import assertk.assertions.containsExactlyInAnyOrder import assertk.assertions.isEqualTo import com.bazel_diff.cli.BazelDiff import org.junit.Rule @@ -53,8 +52,8 @@ class E2ETest { @Test fun testFineGrainedHashExternalRepo() { - // The difference between these two snapshot is simply upgrading Guava version. Following - // is the diff. + // The difference between these two snapshots is simply upgrading the Guava version. + // Following is the diff. // // diff --git a/integration/WORKSPACE b/integration/WORKSPACE // index 617a8d6..2cb3c7d 100644 @@ -107,6 +106,210 @@ class E2ETest { assertThat(actual).isEqualTo(expected) } + @Test + fun testUseCqueryWithExternalDependencyChange() { + // The difference between these two snapshots is simply upgrading the Guava version for Android platform. + // Following is the diff. + // + // diff --git a/WORKSPACE b/WORKSPACE + // index 0fa6bdc..378ba11 100644 + // --- a/WORKSPACE + // +++ b/WORKSPACE + // @@ -27,7 +27,7 @@ maven_install( + // name = "bazel_diff_maven_android", + // artifacts = [ + // "junit:junit:4.12", + // - "com.google.guava:guava:31.0-android", + // + "com.google.guava:guava:32.0.0-android", + // ], + // repositories = [ + // "http://uk.maven.org/maven2", + // + // The project contains the following targets related to the test + // + // java_library( + // name = "guava-user", + // srcs = ["GuavaUser.java"] + select({ + // "//:android_system": ["GuavaUserAndroid.java"], + // "//:jre_system": ["GuavaUserJre.java"], + // }), + // visibility = ["//visibility:public"], + // deps = select({ + // "//:android_system": ["@bazel_diff_maven_android//:com_google_guava_guava"], + // "//:jre_system": ["@bazel_diff_maven//:com_google_guava_guava"], + // }), + // ) + + // java_binary( + // name = "android", + // main_class = "cli.src.test.resources.integration.src.main.java.com.integration.GuavaUser", + // runtime_deps = ["guava-user"], + // target_compatible_with = ["//:android_system"] + // ) + + // java_binary( + // name = "jre", + // main_class = "cli.src.test.resources.integration.src.main.java.com.integration.GuavaUser", + // runtime_deps = ["guava-user"], + // target_compatible_with = ["//:jre_system"] + // ) + // + // So with the above android upgrade, querying changed targets for the `jre` platform should not return anything + // in the user repo changed. Querying changed targets for the `android` platform should only return `guava-user` + // and `android` targets above because `jre` target above is not compatible with the `android` platform. + + val workingDirectoryA = extractFixtureProject("/fixture/cquery-test-base.zip") + val workingDirectoryB = extractFixtureProject("/fixture/cquery-test-guava-upgrade.zip") + val outputDir = temp.newFolder() + val from = File(outputDir, "starting_hashes.json") + val to = File(outputDir, "final_hashes.json") + val impactedTargetsOutput = File(outputDir, "impacted_targets.txt") + + val cli = CommandLine(BazelDiff()) + // Query Android platform + + //From + cli.execute( + "generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:android", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", from.absolutePath + ) + //To + cli.execute( + "generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:android", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", to.absolutePath + ) + //Impacted targets + cli.execute( + "get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath + ) + + var actual: Set = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() + var expected: Set = + javaClass.getResourceAsStream("/fixture/cquery-test-guava-upgrade-android-impacted-targets.txt").use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } + + assertThat(actual).isEqualTo(expected) + + // Query JRE platform + + //From + cli.execute( + "generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:jre", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", from.absolutePath + ) + //To + cli.execute( + "generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:jre", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", to.absolutePath + ) + //Impacted targets + cli.execute( + "get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath + ) + + actual = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() + expected = javaClass.getResourceAsStream("/fixture/cquery-test-guava-upgrade-jre-impacted-targets.txt").use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun testUseCqueryWithAndroidCodeChange() { + // The difference between these two snapshots is simply making a code change to Android-only source code. + // Following is the diff. + // + // diff --git a/src/main/java/com/integration/GuavaUserAndroid.java b/src/main/java/com/integration/GuavaUserAndroid.java + // index 8a9289e..cb645dc 100644 + // --- a/src/main/java/com/integration/GuavaUserAndroid.java + // +++ b/src/main/java/com/integration/GuavaUserAndroid.java + // @@ -2,4 +2,6 @@ package cli.src.test.resources.integration.src.main.java.com.integration; + // + // import com.google.common.collect.ImmutableList; + // + // -public class GuavaUserAndroid {} + // +public class GuavaUserAndroid { + // + // add a comment + // +} + // + // The project contains the following targets related to the test + // + // java_library( + // name = "guava-user", + // srcs = ["GuavaUser.java"] + select({ + // "//:android_system": ["GuavaUserAndroid.java"], + // "//:jre_system": ["GuavaUserJre.java"], + // }), + // visibility = ["//visibility:public"], + // deps = select({ + // "//:android_system": ["@bazel_diff_maven_android//:com_google_guava_guava"], + // "//:jre_system": ["@bazel_diff_maven//:com_google_guava_guava"], + // }), + // ) + + // java_binary( + // name = "android", + // main_class = "cli.src.test.resources.integration.src.main.java.com.integration.GuavaUser", + // runtime_deps = ["guava-user"], + // target_compatible_with = ["//:android_system"] + // ) + + // java_binary( + // name = "jre", + // main_class = "cli.src.test.resources.integration.src.main.java.com.integration.GuavaUser", + // runtime_deps = ["guava-user"], + // target_compatible_with = ["//:jre_system"] + // ) + // + // So with the above android code change, querying changed targets for the `jre` platform should not return + // anything in the user repo changed. Querying changed targets for the `android` platform should only return + // `guava-user` and `android` targets above because `jre` target above is not compatible with the `android` + // platform. + + val workingDirectoryA = extractFixtureProject("/fixture/cquery-test-base.zip") + val workingDirectoryB = extractFixtureProject("/fixture/cquery-test-android-code-change.zip") + val outputDir = temp.newFolder() + val from = File(outputDir, "starting_hashes.json") + val to = File(outputDir, "final_hashes.json") + val impactedTargetsOutput = File(outputDir, "impacted_targets.txt") + + val cli = CommandLine(BazelDiff()) + // Query Android platform + + //From + cli.execute( + "generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:android", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", from.absolutePath + ) + //To + cli.execute( + "generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:android", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", to.absolutePath + ) + //Impacted targets + cli.execute( + "get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath + ) + + var actual: Set = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() + var expected: Set = + javaClass.getResourceAsStream("/fixture/cquery-test-android-code-change-android-impacted-targets.txt").use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } + + assertThat(actual).isEqualTo(expected) + + // Query JRE platform + + //From + cli.execute( + "generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:jre", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", from.absolutePath + ) + //To + cli.execute( + "generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", "bazel", "--useCquery", "--cqueryCommandOptions", "--platforms=//:jre", "--fineGrainedHashExternalRepos", "bazel_diff_maven,bazel_diff_maven_android", to.absolutePath + ) + //Impacted targets + cli.execute( + "get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath + ) + + actual = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() + expected = javaClass.getResourceAsStream("/fixture/cquery-test-android-code-change-jre-impacted-targets.txt").use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } + + assertThat(actual).isEqualTo(expected) + } + private fun extractFixtureProject(path: String): File { val testProject = temp.newFolder() val fixtureCopy = temp.newFile() diff --git a/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt b/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt index e4e2ba3..82bfa8e 100644 --- a/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt @@ -174,7 +174,7 @@ class BuildGraphHasherTest : KoinTest { val target = mock() val rule = mock() whenever(rule.name).thenReturn(name) - whenever(rule.ruleInputList(emptySet())).thenReturn(inputs) + whenever(rule.ruleInputList(false, emptySet())).thenReturn(inputs) whenever(rule.digest).thenReturn(digest.toByteArray()) whenever(target.rule).thenReturn(rule) whenever(target.name).thenReturn(name) diff --git a/cli/src/test/resources/fixture/cquery-test-android-code-change-android-impacted-targets.txt b/cli/src/test/resources/fixture/cquery-test-android-code-change-android-impacted-targets.txt new file mode 100644 index 0000000..a65892a --- /dev/null +++ b/cli/src/test/resources/fixture/cquery-test-android-code-change-android-impacted-targets.txt @@ -0,0 +1,2 @@ +//src/main/java/com/integration:android +//src/main/java/com/integration:guava-user \ No newline at end of file diff --git a/cli/src/test/resources/fixture/cquery-test-android-code-change-jre-impacted-targets.txt b/cli/src/test/resources/fixture/cquery-test-android-code-change-jre-impacted-targets.txt new file mode 100644 index 0000000..e69de29 diff --git a/cli/src/test/resources/fixture/cquery-test-android-code-change.zip b/cli/src/test/resources/fixture/cquery-test-android-code-change.zip new file mode 100644 index 0000000..4d142d7 Binary files /dev/null and b/cli/src/test/resources/fixture/cquery-test-android-code-change.zip differ diff --git a/cli/src/test/resources/fixture/cquery-test-base.zip b/cli/src/test/resources/fixture/cquery-test-base.zip new file mode 100644 index 0000000..9f1a8f9 Binary files /dev/null and b/cli/src/test/resources/fixture/cquery-test-base.zip differ diff --git a/cli/src/test/resources/fixture/cquery-test-guava-upgrade-android-impacted-targets.txt b/cli/src/test/resources/fixture/cquery-test-guava-upgrade-android-impacted-targets.txt new file mode 100644 index 0000000..905b125 --- /dev/null +++ b/cli/src/test/resources/fixture/cquery-test-guava-upgrade-android-impacted-targets.txt @@ -0,0 +1,7 @@ +//external:bazel_diff_maven_android +//src/main/java/com/integration:android +//src/main/java/com/integration:guava-user +@bazel_diff_maven_android//:com_google_errorprone_error_prone_annotations +@bazel_diff_maven_android//:com_google_guava_guava +@bazel_diff_maven_android//:com_google_j2objc_j2objc_annotations +@bazel_diff_maven_android//:org_checkerframework_checker_qual \ No newline at end of file diff --git a/cli/src/test/resources/fixture/cquery-test-guava-upgrade-jre-impacted-targets.txt b/cli/src/test/resources/fixture/cquery-test-guava-upgrade-jre-impacted-targets.txt new file mode 100644 index 0000000..f78740e --- /dev/null +++ b/cli/src/test/resources/fixture/cquery-test-guava-upgrade-jre-impacted-targets.txt @@ -0,0 +1 @@ +//external:bazel_diff_maven_android \ No newline at end of file diff --git a/cli/src/test/resources/fixture/cquery-test-guava-upgrade.zip b/cli/src/test/resources/fixture/cquery-test-guava-upgrade.zip new file mode 100644 index 0000000..4fee280 Binary files /dev/null and b/cli/src/test/resources/fixture/cquery-test-guava-upgrade.zip differ