1// Copyright 2020 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bazel 16 17import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 "sort" 22 "testing" 23 24 analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2" 25 26 "github.com/google/blueprint/metrics" 27 "google.golang.org/protobuf/proto" 28) 29 30func TestAqueryMultiArchGenrule(t *testing.T) { 31 // This input string is retrieved from a real build of bionic-related genrules. 32 const inputString = ` 33{ 34 "Artifacts": [ 35 { "Id": 1, "path_fragment_id": 1 }, 36 { "Id": 2, "path_fragment_id": 6 }, 37 { "Id": 3, "path_fragment_id": 8 }, 38 { "Id": 4, "path_fragment_id": 12 }, 39 { "Id": 5, "path_fragment_id": 19 }, 40 { "Id": 6, "path_fragment_id": 20 }, 41 { "Id": 7, "path_fragment_id": 21 }], 42 "Actions": [{ 43 "target_id": 1, 44 "action_key": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7", 45 "Mnemonic": "Genrule", 46 "configuration_id": 1, 47 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"], 48 "environment_variables": [{ 49 "Key": "PATH", 50 "Value": "/bin:/usr/bin:/usr/local/bin" 51 }], 52 "input_dep_set_ids": [1], 53 "output_ids": [4], 54 "primary_output_id": 4 55 }, { 56 "target_id": 2, 57 "action_key": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826", 58 "Mnemonic": "Genrule", 59 "configuration_id": 1, 60 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"], 61 "environment_variables": [{ 62 "Key": "PATH", 63 "Value": "/bin:/usr/bin:/usr/local/bin" 64 }], 65 "input_dep_set_ids": [2], 66 "output_ids": [5], 67 "primary_output_id": 5 68 }, { 69 "target_id": 3, 70 "action_key": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342", 71 "Mnemonic": "Genrule", 72 "configuration_id": 1, 73 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"], 74 "environment_variables": [{ 75 "Key": "PATH", 76 "Value": "/bin:/usr/bin:/usr/local/bin" 77 }], 78 "input_dep_set_ids": [3], 79 "output_ids": [6], 80 "primary_output_id": 6 81 }, { 82 "target_id": 4, 83 "action_key": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa", 84 "Mnemonic": "Genrule", 85 "configuration_id": 1, 86 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"], 87 "environment_variables": [{ 88 "Key": "PATH", 89 "Value": "/bin:/usr/bin:/usr/local/bin" 90 }], 91 "input_dep_set_ids": [4], 92 "output_ids": [7], 93 "primary_output_id": 7 94 }], 95 "Targets": [ 96 { "Id": 1, "Label": "@sourceroot//bionic/libc:syscalls-arm", "rule_class_id": 1 }, 97 { "Id": 2, "Label": "@sourceroot//bionic/libc:syscalls-x86", "rule_class_id": 1 }, 98 { "Id": 3, "Label": "@sourceroot//bionic/libc:syscalls-x86_64", "rule_class_id": 1 }, 99 { "Id": 4, "Label": "@sourceroot//bionic/libc:syscalls-arm64", "rule_class_id": 1 }], 100 "dep_set_of_files": [ 101 { "Id": 1, "direct_artifact_ids": [1, 2, 3] }, 102 { "Id": 2, "direct_artifact_ids": [1, 2, 3] }, 103 { "Id": 3, "direct_artifact_ids": [1, 2, 3] }, 104 { "Id": 4, "direct_artifact_ids": [1, 2, 3] }], 105 "Configuration": [{ 106 "Id": 1, 107 "Mnemonic": "k8-fastbuild", 108 "platform_name": "k8", 109 "Checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046" 110 }], 111 "rule_classes": [{ "Id": 1, "Name": "genrule"}], 112 "path_fragments": [ 113 { "Id": 5, "Label": ".." }, 114 { "Id": 4, "Label": "sourceroot", "parent_id": 5 }, 115 { "Id": 3, "Label": "bionic", "parent_id": 4 }, 116 { "Id": 2, "Label": "libc", "parent_id": 3 }, 117 { "Id": 1, "Label": "SYSCALLS.TXT", "parent_id": 2 }, 118 { "Id": 7, "Label": "tools", "parent_id": 2 }, 119 { "Id": 6, "Label": "gensyscalls.py", "parent_id": 7 }, 120 { "Id": 11, "Label": "bazel_tools", "parent_id": 5 }, 121 { "Id": 10, "Label": "tools", "parent_id": 11 }, 122 { "Id": 9, "Label": "genrule", "parent_id": 10 }, 123 { "Id": 8, "Label": "genrule-setup.sh", "parent_id": 9 }, 124 { "Id": 18, "Label": "bazel-out" }, 125 { "Id": 17, "Label": "sourceroot", "parent_id": 18 }, 126 { "Id": 16, "Label": "k8-fastbuild", "parent_id": 17 }, 127 { "Id": 15, "Label": "bin", "parent_id": 16 }, 128 { "Id": 14, "Label": "bionic", "parent_id": 15 }, 129 { "Id": 13, "Label": "libc", "parent_id": 14 }, 130 { "Id": 12, "Label": "syscalls-arm.S", "parent_id": 13 }, 131 { "Id": 19, "Label": "syscalls-x86.S", "parent_id": 13 }, 132 { "Id": 20, "Label": "syscalls-x86_64.S", "parent_id": 13 }, 133 { "Id": 21, "Label": "syscalls-arm64.S", "parent_id": 13 }] 134} 135` 136 data, err := JsonToActionGraphContainer(inputString) 137 if err != nil { 138 t.Error(err) 139 return 140 } 141 actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 142 var expectedBuildStatements []*BuildStatement 143 for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} { 144 expectedBuildStatements = append(expectedBuildStatements, 145 &BuildStatement{ 146 Command: fmt.Sprintf( 147 "/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'", 148 arch, arch), 149 OutputPaths: []string{ 150 fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch), 151 }, 152 Env: []*analysis_v2_proto.KeyValuePair{ 153 {Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"}, 154 }, 155 Mnemonic: "Genrule", 156 }) 157 } 158 assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) 159 160 expectedFlattenedInputs := []string{ 161 "../sourceroot/bionic/libc/SYSCALLS.TXT", 162 "../sourceroot/bionic/libc/tools/gensyscalls.py", 163 } 164 // In this example, each depset should have the same expected inputs. 165 for _, actualDepset := range actualDepsets { 166 actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets) 167 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 168 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 169 } 170 } 171} 172 173func TestInvalidOutputId(t *testing.T) { 174 const inputString = ` 175{ 176 "artifacts": [ 177 { "id": 1, "path_fragment_id": 1 }, 178 { "id": 2, "path_fragment_id": 2 }], 179 "actions": [{ 180 "target_id": 1, 181 "action_key": "x", 182 "mnemonic": "x", 183 "arguments": ["touch", "foo"], 184 "input_dep_set_ids": [1], 185 "output_ids": [3], 186 "primary_output_id": 3 187 }], 188 "dep_set_of_files": [ 189 { "id": 1, "direct_artifact_ids": [1, 2] }], 190 "path_fragments": [ 191 { "id": 1, "label": "one" }, 192 { "id": 2, "label": "two" }] 193}` 194 195 data, err := JsonToActionGraphContainer(inputString) 196 if err != nil { 197 t.Error(err) 198 return 199 } 200 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 201 assertError(t, err, "undefined outputId 3") 202} 203 204func TestInvalidInputDepsetIdFromAction(t *testing.T) { 205 const inputString = ` 206{ 207 "artifacts": [ 208 { "id": 1, "path_fragment_id": 1 }, 209 { "id": 2, "path_fragment_id": 2 }], 210 "actions": [{ 211 "target_id": 1, 212 "action_key": "x", 213 "mnemonic": "x", 214 "arguments": ["touch", "foo"], 215 "input_dep_set_ids": [2], 216 "output_ids": [1], 217 "primary_output_id": 1 218 }], 219 "dep_set_of_files": [ 220 { "id": 1, "direct_artifact_ids": [1, 2] }], 221 "path_fragments": [ 222 { "id": 1, "label": "one" }, 223 { "id": 2, "label": "two" }] 224}` 225 226 data, err := JsonToActionGraphContainer(inputString) 227 if err != nil { 228 t.Error(err) 229 return 230 } 231 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 232 assertError(t, err, "undefined (not even empty) input depsetId 2") 233} 234 235func TestInvalidInputDepsetIdFromDepset(t *testing.T) { 236 const inputString = ` 237{ 238 "artifacts": [ 239 { "id": 1, "path_fragment_id": 1 }, 240 { "id": 2, "path_fragment_id": 2 }], 241 "actions": [{ 242 "target_id": 1, 243 "action_key": "x", 244 "mnemonic": "x", 245 "arguments": ["touch", "foo"], 246 "input_dep_set_ids": [1], 247 "output_ids": [1], 248 "primary_output_id": 1 249 }], 250 "dep_set_of_files": [ 251 { "id": 1, "direct_artifact_ids": [1, 2], "transitive_dep_set_ids": [42] }], 252 "path_fragments": [ 253 { "id": 1, "label": "one"}, 254 { "id": 2, "label": "two" }] 255}` 256 257 data, err := JsonToActionGraphContainer(inputString) 258 if err != nil { 259 t.Error(err) 260 return 261 } 262 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 263 assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)") 264} 265 266func TestInvalidInputArtifactId(t *testing.T) { 267 const inputString = ` 268{ 269 "artifacts": [ 270 { "id": 1, "path_fragment_id": 1 }, 271 { "id": 2, "path_fragment_id": 2 }], 272 "actions": [{ 273 "target_id": 1, 274 "action_key": "x", 275 "mnemonic": "x", 276 "arguments": ["touch", "foo"], 277 "input_dep_set_ids": [1], 278 "output_ids": [1], 279 "primary_output_id": 1 280 }], 281 "dep_set_of_files": [ 282 { "id": 1, "direct_artifact_ids": [1, 3] }], 283 "path_fragments": [ 284 { "id": 1, "label": "one" }, 285 { "id": 2, "label": "two" }] 286}` 287 288 data, err := JsonToActionGraphContainer(inputString) 289 if err != nil { 290 t.Error(err) 291 return 292 } 293 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 294 assertError(t, err, "undefined input artifactId 3") 295} 296 297func TestInvalidPathFragmentId(t *testing.T) { 298 const inputString = ` 299{ 300 "artifacts": [ 301 { "id": 1, "path_fragment_id": 1 }, 302 { "id": 2, "path_fragment_id": 2 }], 303 "actions": [{ 304 "target_id": 1, 305 "action_key": "x", 306 "mnemonic": "x", 307 "arguments": ["touch", "foo"], 308 "input_dep_set_ids": [1], 309 "output_ids": [1], 310 "primary_output_id": 1 311 }], 312 "dep_set_of_files": [ 313 { "id": 1, "direct_artifact_ids": [1, 2] }], 314 "path_fragments": [ 315 { "id": 1, "label": "one" }, 316 { "id": 2, "label": "two", "parent_id": 3 }] 317}` 318 319 data, err := JsonToActionGraphContainer(inputString) 320 if err != nil { 321 t.Error(err) 322 return 323 } 324 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 325 assertError(t, err, "undefined path fragment id 3") 326} 327 328func TestDepfiles(t *testing.T) { 329 const inputString = ` 330{ 331 "artifacts": [ 332 { "id": 1, "path_fragment_id": 1 }, 333 { "id": 2, "path_fragment_id": 2 }, 334 { "id": 3, "path_fragment_id": 3 }], 335 "actions": [{ 336 "target_Id": 1, 337 "action_Key": "x", 338 "mnemonic": "x", 339 "arguments": ["touch", "foo"], 340 "input_dep_set_ids": [1], 341 "output_ids": [2, 3], 342 "primary_output_id": 2 343 }], 344 "dep_set_of_files": [ 345 { "id": 1, "direct_Artifact_Ids": [1, 2, 3] }], 346 "path_fragments": [ 347 { "id": 1, "label": "one" }, 348 { "id": 2, "label": "two" }, 349 { "id": 3, "label": "two.d" }] 350}` 351 352 data, err := JsonToActionGraphContainer(inputString) 353 if err != nil { 354 t.Error(err) 355 return 356 } 357 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 358 if err != nil { 359 t.Errorf("Unexpected error %q", err) 360 } 361 if expected := 1; len(actual) != expected { 362 t.Fatalf("Expected %d build statements, got %d", expected, len(actual)) 363 } 364 365 bs := actual[0] 366 expectedDepfile := "two.d" 367 if bs.Depfile == nil { 368 t.Errorf("Expected depfile %q, but there was none found", expectedDepfile) 369 } else if *bs.Depfile != expectedDepfile { 370 t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile) 371 } 372} 373 374func TestMultipleDepfiles(t *testing.T) { 375 const inputString = ` 376{ 377 "artifacts": [ 378 { "id": 1, "path_fragment_id": 1 }, 379 { "id": 2, "path_fragment_id": 2 }, 380 { "id": 3, "path_fragment_id": 3 }, 381 { "id": 4, "path_fragment_id": 4 }], 382 "actions": [{ 383 "target_id": 1, 384 "action_key": "x", 385 "mnemonic": "x", 386 "arguments": ["touch", "foo"], 387 "input_dep_set_ids": [1], 388 "output_ids": [2,3,4], 389 "primary_output_id": 2 390 }], 391 "dep_set_of_files": [{ 392 "id": 1, 393 "direct_artifact_ids": [1, 2, 3, 4] 394 }], 395 "path_fragments": [ 396 { "id": 1, "label": "one" }, 397 { "id": 2, "label": "two" }, 398 { "id": 3, "label": "two.d" }, 399 { "id": 4, "label": "other.d" }] 400}` 401 402 data, err := JsonToActionGraphContainer(inputString) 403 if err != nil { 404 t.Error(err) 405 return 406 } 407 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 408 assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`) 409} 410 411func TestTransitiveInputDepsets(t *testing.T) { 412 // The input aquery for this test comes from a proof-of-concept starlark rule which registers 413 // a single action with many inputs given via a deep depset. 414 const inputString = ` 415{ 416 "artifacts": [ 417 { "id": 1, "path_fragment_id": 1 }, 418 { "id": 2, "path_fragment_id": 7 }, 419 { "id": 3, "path_fragment_id": 8 }, 420 { "id": 4, "path_fragment_id": 9 }, 421 { "id": 5, "path_fragment_id": 10 }, 422 { "id": 6, "path_fragment_id": 11 }, 423 { "id": 7, "path_fragment_id": 12 }, 424 { "id": 8, "path_fragment_id": 13 }, 425 { "id": 9, "path_fragment_id": 14 }, 426 { "id": 10, "path_fragment_id": 15 }, 427 { "id": 11, "path_fragment_id": 16 }, 428 { "id": 12, "path_fragment_id": 17 }, 429 { "id": 13, "path_fragment_id": 18 }, 430 { "id": 14, "path_fragment_id": 19 }, 431 { "id": 15, "path_fragment_id": 20 }, 432 { "id": 16, "path_fragment_id": 21 }, 433 { "id": 17, "path_fragment_id": 22 }, 434 { "id": 18, "path_fragment_id": 23 }, 435 { "id": 19, "path_fragment_id": 24 }, 436 { "id": 20, "path_fragment_id": 25 }, 437 { "id": 21, "path_fragment_id": 26 }], 438 "actions": [{ 439 "target_id": 1, 440 "action_key": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50", 441 "mnemonic": "Action", 442 "configuration_id": 1, 443 "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"], 444 "input_dep_set_ids": [1], 445 "output_ids": [21], 446 "primary_output_id": 21 447 }], 448 "dep_set_of_files": [ 449 { "id": 3, "direct_artifact_ids": [1, 2, 3, 4, 5] }, 450 { "id": 4, "direct_artifact_ids": [6, 7, 8, 9, 10] }, 451 { "id": 2, "transitive_dep_set_ids": [3, 4], "direct_artifact_ids": [11, 12, 13, 14, 15] }, 452 { "id": 5, "direct_artifact_ids": [16, 17, 18, 19] }, 453 { "id": 1, "transitive_dep_set_ids": [2, 5], "direct_artifact_ids": [20] }], 454 "path_fragments": [ 455 { "id": 6, "label": "bazel-out" }, 456 { "id": 5, "label": "sourceroot", "parent_id": 6 }, 457 { "id": 4, "label": "k8-fastbuild", "parent_id": 5 }, 458 { "id": 3, "label": "bin", "parent_id": 4 }, 459 { "id": 2, "label": "testpkg", "parent_id": 3 }, 460 { "id": 1, "label": "test_1", "parent_id": 2 }, 461 { "id": 7, "label": "test_2", "parent_id": 2 }, 462 { "id": 8, "label": "test_3", "parent_id": 2 }, 463 { "id": 9, "label": "test_4", "parent_id": 2 }, 464 { "id": 10, "label": "test_5", "parent_id": 2 }, 465 { "id": 11, "label": "test_6", "parent_id": 2 }, 466 { "id": 12, "label": "test_7", "parent_id": 2 }, 467 { "id": 13, "label": "test_8", "parent_id": 2 }, 468 { "id": 14, "label": "test_9", "parent_id": 2 }, 469 { "id": 15, "label": "test_10", "parent_id": 2 }, 470 { "id": 16, "label": "test_11", "parent_id": 2 }, 471 { "id": 17, "label": "test_12", "parent_id": 2 }, 472 { "id": 18, "label": "test_13", "parent_id": 2 }, 473 { "id": 19, "label": "test_14", "parent_id": 2 }, 474 { "id": 20, "label": "test_15", "parent_id": 2 }, 475 { "id": 21, "label": "test_16", "parent_id": 2 }, 476 { "id": 22, "label": "test_17", "parent_id": 2 }, 477 { "id": 23, "label": "test_18", "parent_id": 2 }, 478 { "id": 24, "label": "test_19", "parent_id": 2 }, 479 { "id": 25, "label": "test_root", "parent_id": 2 }, 480 { "id": 26,"label": "test_out", "parent_id": 2 }] 481}` 482 483 data, err := JsonToActionGraphContainer(inputString) 484 if err != nil { 485 t.Error(err) 486 return 487 } 488 actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 489 490 expectedBuildStatements := []*BuildStatement{ 491 &BuildStatement{ 492 Command: "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'", 493 OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"}, 494 Mnemonic: "Action", 495 SymlinkPaths: []string{}, 496 }, 497 } 498 assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) 499 500 // Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs 501 // are given via a deep depset, but the depset is flattened when returned as a 502 // BuildStatement slice. 503 var expectedFlattenedInputs []string 504 for i := 1; i < 20; i++ { 505 expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i)) 506 } 507 expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root") 508 509 actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes 510 actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets) 511 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 512 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 513 } 514} 515 516func TestSymlinkTree(t *testing.T) { 517 const inputString = ` 518{ 519 "artifacts": [ 520 { "id": 1, "path_fragment_id": 1 }, 521 { "id": 2, "path_fragment_id": 2 }], 522 "actions": [{ 523 "target_id": 1, 524 "action_key": "x", 525 "mnemonic": "SymlinkTree", 526 "configuration_id": 1, 527 "input_dep_set_ids": [1], 528 "output_ids": [2], 529 "primary_output_id": 2, 530 "execution_platform": "//build/bazel/platforms:linux_x86_64" 531 }], 532 "path_fragments": [ 533 { "id": 1, "label": "foo.manifest" }, 534 { "id": 2, "label": "foo.runfiles/MANIFEST" }], 535 "dep_set_of_files": [ 536 { "id": 1, "direct_artifact_ids": [1] }] 537} 538` 539 data, err := JsonToActionGraphContainer(inputString) 540 if err != nil { 541 t.Error(err) 542 return 543 } 544 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 545 if err != nil { 546 t.Errorf("Unexpected error %q", err) 547 } 548 assertBuildStatements(t, []*BuildStatement{ 549 &BuildStatement{ 550 Command: "", 551 OutputPaths: []string{"foo.runfiles/MANIFEST"}, 552 Mnemonic: "SymlinkTree", 553 InputPaths: []string{"foo.manifest"}, 554 SymlinkPaths: []string{}, 555 }, 556 }, actual) 557} 558 559func TestBazelOutRemovalFromInputDepsets(t *testing.T) { 560 const inputString = `{ 561 "artifacts": [ 562 { "id": 1, "path_fragment_id": 10 }, 563 { "id": 2, "path_fragment_id": 20 }, 564 { "id": 3, "path_fragment_id": 30 }, 565 { "id": 4, "path_fragment_id": 40 }], 566 "dep_set_of_files": [{ 567 "id": 1111, 568 "direct_artifact_ids": [3 , 4] 569 }, { 570 "id": 2222, 571 "direct_artifact_ids": [3] 572 }], 573 "actions": [{ 574 "target_id": 100, 575 "action_key": "x", 576 "input_dep_set_ids": [1111, 2222], 577 "mnemonic": "x", 578 "arguments": ["bogus", "command"], 579 "output_ids": [2], 580 "primary_output_id": 1 581 }], 582 "path_fragments": [ 583 { "id": 10, "label": "input" }, 584 { "id": 20, "label": "output" }, 585 { "id": 30, "label": "dep1", "parent_id": 50 }, 586 { "id": 40, "label": "dep2", "parent_id": 60 }, 587 { "id": 50, "label": "bazel_tools", "parent_id": 60 }, 588 { "id": 60, "label": ".."} 589 ] 590}` 591 /* depsets 592 1111 2222 593 / \ | 594 ../dep2 ../bazel_tools/dep1 595 */ 596 data, err := JsonToActionGraphContainer(inputString) 597 if err != nil { 598 t.Error(err) 599 return 600 } 601 actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 602 if len(actualDepsets) != 1 { 603 t.Errorf("expected 1 depset but found %#v", actualDepsets) 604 return 605 } 606 dep2Found := false 607 for _, dep := range flattenDepsets([]string{actualDepsets[0].ContentHash}, actualDepsets) { 608 if dep == "../bazel_tools/dep1" { 609 t.Errorf("dependency %s expected to be removed but still exists", dep) 610 } else if dep == "../dep2" { 611 dep2Found = true 612 } 613 } 614 if !dep2Found { 615 t.Errorf("dependency ../dep2 expected but not found") 616 } 617 618 expectedBuildStatement := &BuildStatement{ 619 Command: "bogus command", 620 OutputPaths: []string{"output"}, 621 Mnemonic: "x", 622 SymlinkPaths: []string{}, 623 } 624 buildStatementFound := false 625 for _, actualBuildStatement := range actualBuildStatements { 626 if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" { 627 buildStatementFound = true 628 break 629 } 630 } 631 if !buildStatementFound { 632 t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements) 633 return 634 } 635} 636 637func TestBazelOutRemovalFromTransitiveInputDepsets(t *testing.T) { 638 const inputString = `{ 639 "artifacts": [ 640 { "id": 1, "path_fragment_id": 10 }, 641 { "id": 2, "path_fragment_id": 20 }, 642 { "id": 3, "path_fragment_id": 30 }], 643 "dep_set_of_files": [{ 644 "id": 1111, 645 "transitive_dep_set_ids": [2222] 646 }, { 647 "id": 2222, 648 "direct_artifact_ids": [3] 649 }, { 650 "id": 3333, 651 "direct_artifact_ids": [3] 652 }, { 653 "id": 4444, 654 "transitive_dep_set_ids": [3333] 655 }], 656 "actions": [{ 657 "target_id": 100, 658 "action_key": "x", 659 "input_dep_set_ids": [1111, 4444], 660 "mnemonic": "x", 661 "arguments": ["bogus", "command"], 662 "output_ids": [2], 663 "primary_output_id": 1 664 }], 665 "path_fragments": [ 666 { "id": 10, "label": "input" }, 667 { "id": 20, "label": "output" }, 668 { "id": 30, "label": "dep", "parent_id": 50 }, 669 { "id": 50, "label": "bazel_tools", "parent_id": 60 }, 670 { "id": 60, "label": ".."} 671 ] 672}` 673 /* depsets 674 1111 4444 675 || || 676 2222 3333 677 | | 678 ../bazel_tools/dep 679 Note: in dep_set_of_files: 680 1111 appears BEFORE its dependency,2222 while 681 4444 appears AFTER its dependency 3333 682 and this test shows that that order doesn't affect empty depset pruning 683 */ 684 data, err := JsonToActionGraphContainer(inputString) 685 if err != nil { 686 t.Error(err) 687 return 688 } 689 actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 690 if len(actualDepsets) != 0 { 691 t.Errorf("expected 0 depsets but found %#v", actualDepsets) 692 return 693 } 694 695 expectedBuildStatement := &BuildStatement{ 696 Command: "bogus command", 697 OutputPaths: []string{"output"}, 698 Mnemonic: "x", 699 } 700 buildStatementFound := false 701 for _, actualBuildStatement := range actualBuildStatements { 702 if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" { 703 buildStatementFound = true 704 break 705 } 706 } 707 if !buildStatementFound { 708 t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements) 709 return 710 } 711} 712 713func TestMiddlemenAction(t *testing.T) { 714 const inputString = ` 715{ 716 "artifacts": [ 717 { "id": 1, "path_fragment_id": 1 }, 718 { "id": 2, "path_fragment_id": 2 }, 719 { "id": 3, "path_fragment_id": 3 }, 720 { "id": 4, "path_fragment_id": 4 }, 721 { "id": 5, "path_fragment_id": 5 }, 722 { "id": 6, "path_fragment_id": 6 }], 723 "path_fragments": [ 724 { "id": 1, "label": "middleinput_one" }, 725 { "id": 2, "label": "middleinput_two" }, 726 { "id": 3, "label": "middleman_artifact" }, 727 { "id": 4, "label": "maininput_one" }, 728 { "id": 5, "label": "maininput_two" }, 729 { "id": 6, "label": "output" }], 730 "dep_set_of_files": [ 731 { "id": 1, "direct_artifact_ids": [1, 2] }, 732 { "id": 2, "direct_artifact_ids": [3, 4, 5] }], 733 "actions": [{ 734 "target_id": 1, 735 "action_key": "x", 736 "mnemonic": "Middleman", 737 "arguments": ["touch", "foo"], 738 "input_dep_set_ids": [1], 739 "output_ids": [3], 740 "primary_output_id": 3 741 }, { 742 "target_id": 2, 743 "action_key": "y", 744 "mnemonic": "Main action", 745 "arguments": ["touch", "foo"], 746 "input_dep_set_ids": [2], 747 "output_ids": [6], 748 "primary_output_id": 6 749 }] 750}` 751 data, err := JsonToActionGraphContainer(inputString) 752 if err != nil { 753 t.Error(err) 754 return 755 } 756 actualBuildStatements, actualDepsets, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 757 if err != nil { 758 t.Errorf("Unexpected error %q", err) 759 } 760 if expected := 2; len(actualBuildStatements) != expected { 761 t.Fatalf("Expected %d build statements, got %d %#v", expected, len(actualBuildStatements), actualBuildStatements) 762 } 763 764 expectedDepsetFiles := [][]string{ 765 {"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}, 766 {"middleinput_one", "middleinput_two"}, 767 } 768 assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles) 769 770 bs := actualBuildStatements[0] 771 if len(bs.InputPaths) > 0 { 772 t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths) 773 } 774 775 expectedOutputs := []string{"output"} 776 if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) { 777 t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths) 778 } 779 780 expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"} 781 actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets) 782 783 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 784 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 785 } 786 787 bs = actualBuildStatements[1] 788 if bs != nil { 789 t.Errorf("Expected nil action for skipped") 790 } 791} 792 793// Returns the contents of given depsets in concatenated post order. 794func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string { 795 depsetsByHash := map[string]AqueryDepset{} 796 for _, depset := range allDepsets { 797 depsetsByHash[depset.ContentHash] = depset 798 } 799 var result []string 800 for _, depsetId := range depsetHashesToFlatten { 801 result = append(result, flattenDepset(depsetId, depsetsByHash)...) 802 } 803 return result 804} 805 806// Returns the contents of a given depset in post order. 807func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string { 808 depset := allDepsets[depsetHashToFlatten] 809 var result []string 810 for _, depsetId := range depset.TransitiveDepSetHashes { 811 result = append(result, flattenDepset(depsetId, allDepsets)...) 812 } 813 result = append(result, depset.DirectArtifacts...) 814 return result 815} 816 817func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) { 818 t.Helper() 819 if len(actualDepsets) != len(expectedDepsetFiles) { 820 t.Errorf("Expected %d depsets, but got %d depsets", len(expectedDepsetFiles), len(actualDepsets)) 821 } 822 for i, actualDepset := range actualDepsets { 823 actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets) 824 if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) { 825 t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs) 826 } 827 } 828} 829 830func TestSimpleSymlink(t *testing.T) { 831 const inputString = ` 832{ 833 "artifacts": [ 834 { "id": 1, "path_fragment_id": 3 }, 835 { "id": 2, "path_fragment_id": 5 }], 836 "actions": [{ 837 "target_id": 1, 838 "action_key": "x", 839 "mnemonic": "Symlink", 840 "input_dep_set_ids": [1], 841 "output_ids": [2], 842 "primary_output_id": 2 843 }], 844 "dep_set_of_files": [ 845 { "id": 1, "direct_artifact_ids": [1] }], 846 "path_fragments": [ 847 { "id": 1, "label": "one" }, 848 { "id": 2, "label": "file_subdir", "parent_id": 1 }, 849 { "id": 3, "label": "file", "parent_id": 2 }, 850 { "id": 4, "label": "symlink_subdir", "parent_id": 1 }, 851 { "id": 5, "label": "symlink", "parent_id": 4 }] 852}` 853 data, err := JsonToActionGraphContainer(inputString) 854 if err != nil { 855 t.Error(err) 856 return 857 } 858 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 859 860 if err != nil { 861 t.Errorf("Unexpected error %q", err) 862 } 863 864 expectedBuildStatements := []*BuildStatement{ 865 &BuildStatement{ 866 Command: "mkdir -p one/symlink_subdir && " + 867 "rm -f one/symlink_subdir/symlink && " + 868 "ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink", 869 InputPaths: []string{"one/file_subdir/file"}, 870 OutputPaths: []string{"one/symlink_subdir/symlink"}, 871 SymlinkPaths: []string{"one/symlink_subdir/symlink"}, 872 Mnemonic: "Symlink", 873 }, 874 } 875 assertBuildStatements(t, actual, expectedBuildStatements) 876} 877 878func TestSymlinkQuotesPaths(t *testing.T) { 879 const inputString = ` 880{ 881 "artifacts": [ 882 { "id": 1, "path_fragment_id": 3 }, 883 { "id": 2, "path_fragment_id": 5 }], 884 "actions": [{ 885 "target_id": 1, 886 "action_key": "x", 887 "mnemonic": "SolibSymlink", 888 "input_dep_set_ids": [1], 889 "output_ids": [2], 890 "primary_output_id": 2 891 }], 892 "dep_set_of_files": [ 893 { "id": 1, "direct_artifact_ids": [1] }], 894 "path_fragments": [ 895 { "id": 1, "label": "one" }, 896 { "id": 2, "label": "file subdir", "parent_id": 1 }, 897 { "id": 3, "label": "file", "parent_id": 2 }, 898 { "id": 4, "label": "symlink subdir", "parent_id": 1 }, 899 { "id": 5, "label": "symlink", "parent_id": 4 }] 900}` 901 902 data, err := JsonToActionGraphContainer(inputString) 903 if err != nil { 904 t.Error(err) 905 return 906 } 907 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 908 if err != nil { 909 t.Errorf("Unexpected error %q", err) 910 } 911 912 expectedBuildStatements := []*BuildStatement{ 913 &BuildStatement{ 914 Command: "mkdir -p 'one/symlink subdir' && " + 915 "rm -f 'one/symlink subdir/symlink' && " + 916 "ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'", 917 InputPaths: []string{"one/file subdir/file"}, 918 OutputPaths: []string{"one/symlink subdir/symlink"}, 919 SymlinkPaths: []string{"one/symlink subdir/symlink"}, 920 Mnemonic: "SolibSymlink", 921 }, 922 } 923 assertBuildStatements(t, expectedBuildStatements, actual) 924} 925 926func TestSymlinkMultipleInputs(t *testing.T) { 927 const inputString = ` 928{ 929 "artifacts": [ 930 { "id": 1, "path_fragment_id": 1 }, 931 { "id": 2, "path_fragment_id": 2 }, 932 { "id": 3, "path_fragment_id": 3 }], 933 "actions": [{ 934 "target_id": 1, 935 "action_key": "x", 936 "mnemonic": "Symlink", 937 "input_dep_set_ids": [1], 938 "output_ids": [3], 939 "primary_output_id": 3 940 }], 941 "dep_set_of_files": [{ "id": 1, "direct_artifact_ids": [1,2] }], 942 "path_fragments": [ 943 { "id": 1, "label": "file" }, 944 { "id": 2, "label": "other_file" }, 945 { "id": 3, "label": "symlink" }] 946}` 947 948 data, err := JsonToActionGraphContainer(inputString) 949 if err != nil { 950 t.Error(err) 951 return 952 } 953 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 954 assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`) 955} 956 957func TestSymlinkMultipleOutputs(t *testing.T) { 958 const inputString = ` 959{ 960 "artifacts": [ 961 { "id": 1, "path_fragment_id": 1 }, 962 { "id": 3, "path_fragment_id": 3 }], 963 "actions": [{ 964 "target_id": 1, 965 "action_key": "x", 966 "mnemonic": "Symlink", 967 "input_dep_set_ids": [1], 968 "output_ids": [2,3], 969 "primary_output_id": 2 970 }], 971 "dep_set_of_files": [ 972 { "id": 1, "direct_artifact_ids": [1] }], 973 "path_fragments": [ 974 { "id": 1, "label": "file" }, 975 { "id": 2, "label": "symlink" }, 976 { "id": 3, "label": "other_symlink" }] 977}` 978 979 data, err := JsonToActionGraphContainer(inputString) 980 if err != nil { 981 t.Error(err) 982 return 983 } 984 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 985 assertError(t, err, "undefined outputId 2") 986} 987 988func TestTemplateExpandActionSubstitutions(t *testing.T) { 989 const inputString = ` 990{ 991 "artifacts": [{ 992 "id": 1, 993 "path_fragment_id": 1 994 }], 995 "actions": [{ 996 "target_id": 1, 997 "action_key": "x", 998 "mnemonic": "TemplateExpand", 999 "configuration_id": 1, 1000 "output_ids": [1], 1001 "primary_output_id": 1, 1002 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1003 "template_content": "Test template substitutions: %token1%, %python_binary%", 1004 "substitutions": [ 1005 { "key": "%token1%", "value": "abcd" }, 1006 { "key": "%python_binary%", "value": "python3" }] 1007 }], 1008 "path_fragments": [ 1009 { "id": 1, "label": "template_file" }] 1010}` 1011 1012 data, err := JsonToActionGraphContainer(inputString) 1013 if err != nil { 1014 t.Error(err) 1015 return 1016 } 1017 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1018 if err != nil { 1019 t.Errorf("Unexpected error %q", err) 1020 } 1021 1022 expectedBuildStatements := []*BuildStatement{ 1023 &BuildStatement{ 1024 Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " + 1025 "chmod a+x template_file'", 1026 OutputPaths: []string{"template_file"}, 1027 Mnemonic: "TemplateExpand", 1028 SymlinkPaths: []string{}, 1029 }, 1030 } 1031 assertBuildStatements(t, expectedBuildStatements, actual) 1032} 1033 1034func TestTemplateExpandActionNoOutput(t *testing.T) { 1035 const inputString = ` 1036{ 1037 "artifacts": [ 1038 { "id": 1, "path_fragment_id": 1 }], 1039 "actions": [{ 1040 "target_id": 1, 1041 "action_key": "x", 1042 "mnemonic": "TemplateExpand", 1043 "configuration_id": 1, 1044 "primary_output_id": 1, 1045 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1046 "templateContent": "Test template substitutions: %token1%, %python_binary%", 1047 "substitutions": [ 1048 { "key": "%token1%", "value": "abcd" }, 1049 { "key": "%python_binary%", "value": "python3" }] 1050 }], 1051 "path_fragments": [ 1052 { "id": 1, "label": "template_file" }] 1053}` 1054 1055 data, err := JsonToActionGraphContainer(inputString) 1056 if err != nil { 1057 t.Error(err) 1058 return 1059 } 1060 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 1061 assertError(t, err, `Expect 1 output to template expand action, got: output []`) 1062} 1063 1064func TestFileWrite(t *testing.T) { 1065 const inputString = ` 1066{ 1067 "artifacts": [ 1068 { "id": 1, "path_fragment_id": 1 }], 1069 "actions": [{ 1070 "target_id": 1, 1071 "action_key": "x", 1072 "mnemonic": "FileWrite", 1073 "configuration_id": 1, 1074 "output_ids": [1], 1075 "primary_output_id": 1, 1076 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1077 "file_contents": "file data\n" 1078 }], 1079 "path_fragments": [ 1080 { "id": 1, "label": "foo.manifest" }] 1081} 1082` 1083 data, err := JsonToActionGraphContainer(inputString) 1084 if err != nil { 1085 t.Error(err) 1086 return 1087 } 1088 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1089 if err != nil { 1090 t.Errorf("Unexpected error %q", err) 1091 } 1092 assertBuildStatements(t, []*BuildStatement{ 1093 &BuildStatement{ 1094 OutputPaths: []string{"foo.manifest"}, 1095 Mnemonic: "FileWrite", 1096 FileContents: "file data\n", 1097 SymlinkPaths: []string{}, 1098 }, 1099 }, actual) 1100} 1101 1102func TestSourceSymlinkManifest(t *testing.T) { 1103 const inputString = ` 1104{ 1105 "artifacts": [ 1106 { "id": 1, "path_fragment_id": 1 }], 1107 "actions": [{ 1108 "target_id": 1, 1109 "action_key": "x", 1110 "mnemonic": "SourceSymlinkManifest", 1111 "configuration_id": 1, 1112 "output_ids": [1], 1113 "primary_output_id": 1, 1114 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1115 "file_contents": "symlink target\n" 1116 }], 1117 "path_fragments": [ 1118 { "id": 1, "label": "foo.manifest" }] 1119} 1120` 1121 data, err := JsonToActionGraphContainer(inputString) 1122 if err != nil { 1123 t.Error(err) 1124 return 1125 } 1126 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1127 if err != nil { 1128 t.Errorf("Unexpected error %q", err) 1129 } 1130 assertBuildStatements(t, []*BuildStatement{ 1131 &BuildStatement{ 1132 OutputPaths: []string{"foo.manifest"}, 1133 Mnemonic: "SourceSymlinkManifest", 1134 SymlinkPaths: []string{}, 1135 }, 1136 }, actual) 1137} 1138 1139func assertError(t *testing.T, err error, expected string) { 1140 t.Helper() 1141 if err == nil { 1142 t.Errorf("expected error '%s', but got no error", expected) 1143 } else if err.Error() != expected { 1144 t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error()) 1145 } 1146} 1147 1148// Asserts that the given actual build statements match the given expected build statements. 1149// Build statement equivalence is determined using buildStatementEquals. 1150func assertBuildStatements(t *testing.T, expected []*BuildStatement, actual []*BuildStatement) { 1151 t.Helper() 1152 if len(expected) != len(actual) { 1153 t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v", 1154 len(expected), len(actual), expected, actual) 1155 return 1156 } 1157 type compareFn = func(i int, j int) bool 1158 byCommand := func(slice []*BuildStatement) compareFn { 1159 return func(i int, j int) bool { 1160 if slice[i] == nil { 1161 return false 1162 } else if slice[j] == nil { 1163 return false 1164 } 1165 return slice[i].Command < slice[j].Command 1166 } 1167 } 1168 sort.SliceStable(expected, byCommand(expected)) 1169 sort.SliceStable(actual, byCommand(actual)) 1170 for i, actualStatement := range actual { 1171 expectedStatement := expected[i] 1172 if differingField := buildStatementEquals(actualStatement, expectedStatement); differingField != "" { 1173 t.Errorf("%s differs\nunexpected build statement %#v.\nexpected: %#v", 1174 differingField, actualStatement, expectedStatement) 1175 return 1176 } 1177 } 1178} 1179 1180func buildStatementEquals(first *BuildStatement, second *BuildStatement) string { 1181 if (first == nil) != (second == nil) { 1182 return "Nil" 1183 } 1184 if first.Mnemonic != second.Mnemonic { 1185 return "Mnemonic" 1186 } 1187 if first.Command != second.Command { 1188 return "Command" 1189 } 1190 // Ordering is significant for environment variables. 1191 if !reflect.DeepEqual(first.Env, second.Env) { 1192 return "Env" 1193 } 1194 // Ordering is irrelevant for input and output paths, so compare sets. 1195 if !reflect.DeepEqual(sortedStrings(first.InputPaths), sortedStrings(second.InputPaths)) { 1196 return "InputPaths" 1197 } 1198 if !reflect.DeepEqual(sortedStrings(first.OutputPaths), sortedStrings(second.OutputPaths)) { 1199 return "OutputPaths" 1200 } 1201 if !reflect.DeepEqual(sortedStrings(first.SymlinkPaths), sortedStrings(second.SymlinkPaths)) { 1202 return "SymlinkPaths" 1203 } 1204 if first.Depfile != second.Depfile { 1205 return "Depfile" 1206 } 1207 return "" 1208} 1209 1210func sortedStrings(stringSlice []string) []string { 1211 sorted := make([]string, len(stringSlice)) 1212 copy(sorted, stringSlice) 1213 sort.Strings(sorted) 1214 return sorted 1215} 1216 1217// Transform the json format to ActionGraphContainer 1218func JsonToActionGraphContainer(inputString string) ([]byte, error) { 1219 var aqueryProtoResult analysis_v2_proto.ActionGraphContainer 1220 err := json.Unmarshal([]byte(inputString), &aqueryProtoResult) 1221 if err != nil { 1222 return []byte(""), err 1223 } 1224 data, _ := proto.Marshal(&aqueryProtoResult) 1225 return data, err 1226} 1227