From 0a60a58a96f5baacd4d80c87759c048362e07e78 Mon Sep 17 00:00:00 2001 From: Boris <125319243+bz-canva@users.noreply.github.com> Date: Tue, 10 Oct 2023 05:28:32 +1100 Subject: [PATCH] Add `--includTargetType` and `--targetType` options (#194) * Write type to output as well * Add --includeTargetType and --targetType options --- README.md | 44 ++++++++++++++--- .../bazel_diff/cli/GenerateHashesCommand.kt | 20 +++++++- .../cli/GetImpactedTargetsCommand.kt | 14 ++++-- .../com/bazel_diff/hash/BuildGraphHasher.kt | 12 ++--- .../kotlin/com/bazel_diff/hash/TargetHash.kt | 18 +++++++ .../interactor/DeserialiseHashesInteractor.kt | 17 ++++++- .../interactor/GenerateHashesInteractor.kt | 16 +++++-- .../test/kotlin/com/bazel_diff/e2e/E2ETest.kt | 28 ++++++++--- .../bazel_diff/hash/BuildGraphHasherTest.kt | 24 +++++----- .../DeserialiseHashesInteractorTest.kt | 48 ++++++++++++++++++- .../impacted_targets-1-2-rule-sourcefile.txt | 3 ++ 11 files changed, 200 insertions(+), 44 deletions(-) create mode 100644 cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt create mode 100644 cli/src/test/resources/fixture/impacted_targets-1-2-rule-sourcefile.txt diff --git a/README.md b/README.md index 770a44d..8d4da62 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Commands: ```terminal Usage: bazel-diff generate-hashes [-hkvV] [--[no-]useCquery] [-b=] + [--[no-]includeTargetType] [--contentHashPath=] [-s=] -w= [-co=]... @@ -92,10 +93,33 @@ Usage: bazel-diff generate-hashes [-hkvV] [--[no-]useCquery] [-b=] [-so=]... Writes to a file the SHA256 hashes for each Bazel Target in the provided workspace. - The filepath to write the resulting JSON of - dictionary target => SHA-256 values. If not - specified, the JSON will be written to STDOUT. - -b, --bazelPath= + The filepath to write the resulting JSON to. + If not specified, the JSON will be written to STDOUT. + + By default the JSON schema is a dictionary of target => SHA-256 values. + Example: + { + "//cli:bazel-diff_deploy.jar": "4ae310f8ad2bc728934e3509b6102ca658e828b9cd668f79990e95c6663f9633" + ... + } + + If --includeTargetType is specified, the JSON schema will include the target type (SourceFile/Rule/GeneratedFile) + Example: + { + "//cli:src/test/resources/fixture/integration-test-1.zip": "SourceFile#c259eba8539f4c14e4536c61975457c2990e090067893f4a2981e7bb5f4ef4cf", + "//external:android_gmaven_r8": "Rule#795f583449a40814c05e1cc5d833002afed8d12bce5b835579c7f139c2462d61", + "//cli:bazel-diff_deploy.jar": "GeneratedFile#4ae310f8ad2bc728934e3509b6102ca658e828b9cd668f79990e95c6663f9633", + ... + } + ----[no-]includeTargetType + Whether include target type in the generated JSON or not. + If false, the generate JSON schema is: {"": ""} + If true, the generate JSON schema is: {"": "#" + -tt, --targetType= + The type of targets to filter, available options are SourceFile/Rule/GeneratedFile + Only works if the JSON was generated with `--includeTargetType` enabled. + If not specified, all types of impacted targets will be returned. + -b, --bazelPath= Path to Bazel binary. If not specified, the Bazel binary available in PATH will be used. -co, --bazelCommandOptions= @@ -157,7 +181,7 @@ 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 -are the summation of the of the rule implementation hash, the SHA256 value +are the summation of the rule implementation hash, the SHA256 value for every attribute of the rule and then the summation of the SHA256 value for all `rule_inputs` using the same exact algorithm. For source_file inputs the content of the file are converted into a SHA256 value. @@ -167,18 +191,24 @@ content of the file are converted into a SHA256 value. ```terminal Usage: bazel-diff get-impacted-targets [-v] -fh= -o= + -tt= -sh= Command-line utility to analyze the state of the bazel build graph -fh, --finalHashes= The path to the JSON file of target hashes for the final revision. Run 'generate-hashes' to get this value. - -o, --output= + -o, --output= Filepath to write the impacted Bazel targets to, newline separated -sh, --startingHashes= The path to the JSON file of target hashes for the initial revision. Run 'generate-hashes' to get this value. - -v, --verbose Display query string, missing files and elapsed time + -tt, --targetType= + The type of targets to filter, available options are SourceFile/Rule/GeneratedFile + Only works if the JSON was generated with `--includeTargetType` enabled. + If not specified, all types of impacted targets will be returned. + -v, --verbose + Display query string, missing files and elapsed time ``` ## Installing 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 8babffe..4985482 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GenerateHashesCommand.kt @@ -81,6 +81,24 @@ class GenerateHashesCommand : Callable { ) var useCquery = false + @CommandLine.Option( + names = ["--includeTargetType"], + negatable = true, + description = ["Whether include target type in the generated JSON or not.\n" + + "If false, the generate JSON schema is: {\"\": \"\"}\n" + + "If true, the generate JSON schema is: {\"\": \"#\" }"], + scope = CommandLine.ScopeType.INHERIT + ) + var includeTargetType = false + + @CommandLine.Option( + names = ["-tt", "--targetType"], + split = ",", + scope = CommandLine.ScopeType.LOCAL, + description = ["The types of targets to filter. Use comma (,) to separate multiple values, e.g. '--targetType=SourceFile,Rule,GeneratedFile'."] + ) + var targetType: Set? = null + @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."], @@ -142,7 +160,7 @@ class GenerateHashesCommand : Callable { ) } - return when (GenerateHashesInteractor().execute(seedFilepaths, outputPath, ignoredRuleHashingAttributes)) { + return when (GenerateHashesInteractor().execute(seedFilepaths, outputPath, ignoredRuleHashingAttributes, targetType, includeTargetType)) { true -> CommandLine.ExitCode.OK false -> CommandLine.ExitCode.SOFTWARE }.also { stopKoin() } diff --git a/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt b/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt index 6847bae..cd8c4e7 100644 --- a/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt +++ b/cli/src/main/kotlin/com/bazel_diff/cli/GetImpactedTargetsCommand.kt @@ -38,6 +38,14 @@ class GetImpactedTargetsCommand : Callable { ) lateinit var finalHashesJSONPath: File + @CommandLine.Option( + names = ["-tt", "--targetType"], + split = ",", + scope = CommandLine.ScopeType.LOCAL, + description = ["The types of targets to filter. Use comma (,) to separate multiple values, e.g. '--targetType=SourceFile,Rule,GeneratedFile'."] + ) + var targetType: Set? = null + @CommandLine.Option( names = ["-o", "--output"], scope = CommandLine.ScopeType.LOCAL, @@ -58,8 +66,8 @@ class GetImpactedTargetsCommand : Callable { validate() val deserialiser = DeserialiseHashesInteractor() - val from = deserialiser.execute(startingHashesJSONPath) - val to = deserialiser.execute(finalHashesJSONPath) + val from = deserialiser.execute(startingHashesJSONPath, targetType) + val to = deserialiser.execute(finalHashesJSONPath, targetType) val impactedTargets = CalculateImpactedTargetsInteractor().execute(from, to) @@ -70,7 +78,7 @@ class GetImpactedTargetsCommand : Callable { }).use { writer -> impactedTargets.forEach { writer.write(it) - //Should not be depend on OS + //Should not depend on OS writer.write("\n") } } diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt b/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt index 12b8f48..2c85a15 100644 --- a/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt +++ b/cli/src/main/kotlin/com/bazel_diff/hash/BuildGraphHasher.kt @@ -29,7 +29,7 @@ class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { fun hashAllBazelTargetsAndSourcefiles( seedFilepaths: Set = emptySet(), ignoredAttrs: Set = emptySet() - ): Map { + ): Map { /** * Bazel will lock parallel queries but this is still allowing us to hash source files while executing a parallel query */ @@ -104,7 +104,7 @@ class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { sourceDigests: ConcurrentMap, allTargets: List, ignoredAttrs: Set - ): Map { + ): Map { val ruleHashes: ConcurrentMap = ConcurrentHashMap() val targetToRule: MutableMap = HashMap() traverseGraph(allTargets, targetToRule) @@ -119,13 +119,13 @@ class BuildGraphHasher(private val bazelClient: BazelClient) : KoinComponent { seedHash, ignoredAttrs ) - Pair(target.name, targetDigest.toHexString()) + Pair(target.name, TargetHash(target.javaClass.name.substringAfterLast('$'), targetDigest.toHexString())) } - .filter { targetEntry: Pair? -> targetEntry != null } + .filter { targetEntry: Pair? -> targetEntry != null } .collect( Collectors.toMap( - { obj: Pair -> obj.first }, - { obj: Pair -> obj.second }, + { obj: Pair -> obj.first }, + { obj: Pair -> obj.second }, ) ) } diff --git a/cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt b/cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt new file mode 100644 index 0000000..78e9d54 --- /dev/null +++ b/cli/src/main/kotlin/com/bazel_diff/hash/TargetHash.kt @@ -0,0 +1,18 @@ +package com.bazel_diff.hash + +data class TargetHash( + val type: String, // Rule/GeneratedFile/SourceFile/... + val hash: String +) { + val hashWithType by lazy { + "${type}#${hash}" + } + + fun toJson(includeTargetType: Boolean): String { + return if (includeTargetType) { + hashWithType + } else { + hash + } + } +} \ No newline at end of file diff --git a/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt b/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt index d4be7db..02e9b63 100644 --- a/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt +++ b/cli/src/main/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractor.kt @@ -12,9 +12,22 @@ class DeserialiseHashesInteractor : KoinComponent { /** * @param file path to file that has been pre-validated + * @param targetTypes the target types to filter. If null, all targets will be returned */ - fun execute(file: File): Map { + fun execute(file: File, targetTypes: Set? = null): Map { val shape = object : TypeToken>() {}.type - return gson.fromJson(FileReader(file), shape) + val result: Map = gson.fromJson(FileReader(file), shape) + if (targetTypes == null) { + return result.mapValues { it.value.substringAfter("#") } + } else { + val prefixes = targetTypes.map { "${it}#" }.toSet() + return result.filter { entry -> + if (entry.value.contains("#")) { + prefixes.any { entry.value.startsWith(it) } + } else { + throw IllegalStateException("No type info found in ${file}, please re-generate the JSON with --includeTypeTarget!") + } + }.mapValues { it.value.substringAfter("#") } + } } } diff --git a/cli/src/main/kotlin/com/bazel_diff/interactor/GenerateHashesInteractor.kt b/cli/src/main/kotlin/com/bazel_diff/interactor/GenerateHashesInteractor.kt index 453c392..f686ffb 100644 --- a/cli/src/main/kotlin/com/bazel_diff/interactor/GenerateHashesInteractor.kt +++ b/cli/src/main/kotlin/com/bazel_diff/interactor/GenerateHashesInteractor.kt @@ -18,10 +18,10 @@ class GenerateHashesInteractor : KoinComponent { private val logger: Logger by inject() private val gson: Gson by inject() - fun execute(seedFilepaths: File?, outputPath: File?, ignoredRuleHashingAttributes: Set): Boolean { + fun execute(seedFilepaths: File?, outputPath: File?, ignoredRuleHashingAttributes: Set, targetTypes:Set?, includeTargetType: Boolean = false): Boolean { return try { val epoch = Calendar.getInstance().getTimeInMillis() - var seedFilepathsSet: Set = when { + val seedFilepathsSet: Set = when { seedFilepaths != null -> { BufferedReader(FileReader(seedFilepaths)).use { it.readLines() @@ -34,12 +34,18 @@ class GenerateHashesInteractor : KoinComponent { val hashes = buildGraphHasher.hashAllBazelTargetsAndSourcefiles( seedFilepathsSet, ignoredRuleHashingAttributes - ) + ).let { + if(targetTypes == null) { + it + } else { + it.filter { targetTypes.contains(it.value.type) } + } + } when (outputPath) { null -> FileWriter(FileDescriptor.out) else -> FileWriter(outputPath) - }.use { - it.write(gson.toJson(hashes)) + }.use { fileWriter -> + fileWriter.write(gson.toJson(hashes.mapValues { it.value.toJson(includeTargetType) })) } val duration = Calendar.getInstance().getTimeInMillis() - epoch; logger.i { "generate-hashes finished in $duration" } 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 001779c..a48f15b 100644 --- a/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt @@ -16,8 +16,9 @@ class E2ETest { @get:Rule val temp: TemporaryFolder = TemporaryFolder() - @Test - fun testE2E() { + private fun CommandLine.execute(args: List) = execute(*args.toTypedArray()) + + private fun testE2E(extraGenerateHashesArgs: List, extraGetImpactedTargetsArgs: List, expectedResultFile: String) { val projectA = extractFixtureProject("/fixture/integration-test-1.zip") val projectB = extractFixtureProject("/fixture/integration-test-2.zip") @@ -32,24 +33,39 @@ class E2ETest { val cli = CommandLine(BazelDiff()) //From cli.execute( - "generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", bazelPath, from.absolutePath + listOf("generate-hashes", "-w", workingDirectoryA.absolutePath, "-b", bazelPath, from.absolutePath) + extraGenerateHashesArgs ) //To cli.execute( - "generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", bazelPath, to.absolutePath + listOf("generate-hashes", "-w", workingDirectoryB.absolutePath, "-b", bazelPath, to.absolutePath) + extraGenerateHashesArgs ) //Impacted targets cli.execute( - "get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath + listOf("get-impacted-targets", "-sh", from.absolutePath, "-fh", to.absolutePath, "-o", impactedTargetsOutput.absolutePath) + extraGetImpactedTargetsArgs ) val actual: Set = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet() val expected: Set = - javaClass.getResourceAsStream("/fixture/impacted_targets-1-2.txt").use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } + javaClass.getResourceAsStream(expectedResultFile).use { it.bufferedReader().readLines().filter { it.isNotBlank() }.toSet() } assertThat(actual).isEqualTo(expected) } + @Test + fun testE2E() { + testE2E(emptyList(), emptyList(), "/fixture/impacted_targets-1-2.txt") + } + + @Test + fun testE2EIncludingTargetType() { + testE2E(listOf("-tt", "Rule,SourceFile"), emptyList(), "/fixture/impacted_targets-1-2-rule-sourcefile.txt") + } + + @Test + fun testE2EWithTargetType() { + testE2E(listOf("--includeTargetType"), listOf("-tt", "Rule,SourceFile"), "/fixture/impacted_targets-1-2-rule-sourcefile.txt") + } + @Test fun testFineGrainedHashExternalRepo() { // The difference between these two snapshots is simply upgrading the Guava version. 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 854e388..7819ad8 100644 --- a/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/hash/BuildGraphHasherTest.kt @@ -73,8 +73,8 @@ class BuildGraphHasherTest : KoinTest { val hash = hasher.hashAllBazelTargetsAndSourcefiles() assertThat(hash).containsOnly( - "rule1" to "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775", - "rule2" to "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9", + "rule1" to TargetHash("Rule", "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775"), + "rule2" to TargetHash("Rule", "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9"), ) } @@ -86,8 +86,8 @@ class BuildGraphHasherTest : KoinTest { whenever(bazelClientMock.queryAllSourcefileTargets()).thenReturn(emptyList()) val hash = hasher.hashAllBazelTargetsAndSourcefiles(seedFilepaths) assertThat(hash).containsOnly( - "rule1" to "0404d80eadcc2dbfe9f0d7935086e1115344a06bd76d4e16af0dfd7b4913ee60", - "rule2" to "6fe63fa16340d18176e6d6021972c65413441b72135247179362763508ebddfe", + "rule1" to TargetHash("Rule", "0404d80eadcc2dbfe9f0d7935086e1115344a06bd76d4e16af0dfd7b4913ee60"), + "rule2" to TargetHash("Rule", "6fe63fa16340d18176e6d6021972c65413441b72135247179362763508ebddfe"), ) } @@ -103,10 +103,10 @@ class BuildGraphHasherTest : KoinTest { whenever(bazelClientMock.queryAllSourcefileTargets()).thenReturn(emptyList()) val hash = hasher.hashAllBazelTargetsAndSourcefiles() assertThat(hash).containsOnly( - "rule1" to "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775", - "rule2" to "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9", - "rule3" to "87dd050f1ca0f684f37970092ff6a02677d995718b5a05461706c0f41ffd4915", - "rule4" to "a7bc5d23cd98c4942dc879c649eb9646e38eddd773f9c7996fa0d96048cf63dc", + "rule1" to TargetHash("Rule", "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775"), + "rule2" to TargetHash("Rule", "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9"), + "rule3" to TargetHash("Rule", "87dd050f1ca0f684f37970092ff6a02677d995718b5a05461706c0f41ffd4915"), + "rule4" to TargetHash("Rule", "a7bc5d23cd98c4942dc879c649eb9646e38eddd773f9c7996fa0d96048cf63dc"), ) } @@ -122,10 +122,10 @@ class BuildGraphHasherTest : KoinTest { whenever(bazelClientMock.queryAllSourcefileTargets()).thenReturn(emptyList()) val hash = hasher.hashAllBazelTargetsAndSourcefiles() assertThat(hash).containsOnly( - "rule1" to "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775", - "rule2" to "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9", - "rule3" to "ca2f970a5a5a18730d7633cc32b48b1d94679f4ccaea56c4924e1f9913bd9cb5", - "rule4" to "bf15e616e870aaacb02493ea0b8e90c6c750c266fa26375e22b30b78954ee523", + "rule1" to TargetHash("Rule", "2c963f7c06bc1cead7e3b4759e1472383d4469fc3238dc42f8848190887b4775"), + "rule2" to TargetHash("Rule", "bdc1abd0a07103cea34199a9c0d1020619136ff90fb88dcc3a8f873c811c1fe9"), + "rule3" to TargetHash("Rule", "ca2f970a5a5a18730d7633cc32b48b1d94679f4ccaea56c4924e1f9913bd9cb5"), + "rule4" to TargetHash("Rule", "bf15e616e870aaacb02493ea0b8e90c6c750c266fa26375e22b30b78954ee523"), ) } diff --git a/cli/src/test/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractorTest.kt b/cli/src/test/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractorTest.kt index cf3671e..da781d3 100644 --- a/cli/src/test/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractorTest.kt +++ b/cli/src/test/kotlin/com/bazel_diff/interactor/DeserialiseHashesInteractorTest.kt @@ -1,7 +1,10 @@ package com.bazel_diff.interactor import assertk.assertThat +import assertk.assertions.hasClass import assertk.assertions.isEqualTo +import assertk.assertions.isFailure +import assertk.assertions.messageContains import com.bazel_diff.testModule import org.junit.Rule import org.junit.Test @@ -22,15 +25,56 @@ class DeserialiseHashesInteractorTest : KoinTest { @get:Rule val temp: TemporaryFolder = TemporaryFolder() + val interactor = DeserialiseHashesInteractor() + @Test fun testDeserialisation() { val file = temp.newFile().apply { - writeText("{\"target-name\":\"hash\"}") + writeText("""{"target-name":"hash"}""") } - val actual = DeserialiseHashesInteractor().execute(file) + val actual = interactor.execute(file) assertThat(actual).isEqualTo(mapOf( "target-name" to "hash" )) } + + @Test + fun testDeserialisatingFileWithoutType() { + val file = temp.newFile().apply { + writeText("""{"target-name":"hash"}""") + } + + assertThat { interactor.execute(file, setOf("Whatever"))} + .isFailure().apply { + messageContains("please re-generate the JSON with --includeTypeTarget!") + hasClass(IllegalStateException::class) + } + } + + @Test + fun testDeserialisationWithType() { + val file = temp.newFile().apply { + writeText("""{ + | "target-1":"GeneratedFile#hash1", + | "target-2":"Rule#hash2", + | "target-3":"SourceFile#hash3" + |}""".trimMargin()) + } + + assertThat(interactor.execute(file, null)).isEqualTo(mapOf( + "target-1" to "hash1", + "target-2" to "hash2", + "target-3" to "hash3" + )) + assertThat(interactor.execute(file, setOf("GeneratedFile"))).isEqualTo(mapOf( + "target-1" to "hash1" + )) + assertThat(interactor.execute(file, setOf("Rule"))).isEqualTo(mapOf( + "target-2" to "hash2" + )) + assertThat(interactor.execute(file, setOf("SourceFile"))).isEqualTo(mapOf( + "target-3" to "hash3" + )) + } } diff --git a/cli/src/test/resources/fixture/impacted_targets-1-2-rule-sourcefile.txt b/cli/src/test/resources/fixture/impacted_targets-1-2-rule-sourcefile.txt new file mode 100644 index 0000000..dc0cdc5 --- /dev/null +++ b/cli/src/test/resources/fixture/impacted_targets-1-2-rule-sourcefile.txt @@ -0,0 +1,3 @@ +//test/java/com/integration:bazel-diff-integration-test-lib +//test/java/com/integration:bazel-diff-integration-tests +//test/java/com/integration:TestStringGenerator.java \ No newline at end of file