1// Copyright 2021 The Bazel Authors. 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 hermeticity_test 16 17import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "strings" 22 "testing" 23 24 "github.com/bazelbuild/rules_go/go/tools/bazel_testing" 25) 26 27func TestMain(m *testing.M) { 28 bazel_testing.TestMain(m, bazel_testing.Args{ 29 Main: ` 30-- BUILD.bazel -- 31load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") 32load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 33load("@rules_proto//proto:defs.bzl", "proto_library") 34 35go_binary( 36 name = "main", 37 srcs = [ 38 "main.go", 39 ":gen_go", 40 ], 41 data = [":helper"], 42 embedsrcs = [":helper"], 43 cdeps = [":helper"], 44 cgo = True, 45 linkmode = "c-archive", 46 gotags = ["foo"], 47 deps = [":lib"], 48) 49 50go_library( 51 name = "lib", 52 srcs = [ 53 "lib.go", 54 ":gen_indirect_go", 55 ], 56 importpath = "example.com/lib", 57 data = [":indirect_helper"], 58 embedsrcs = [":indirect_helper"], 59 cdeps = [":indirect_helper"], 60 cgo = True, 61) 62 63go_test( 64 name = "main_test", 65 srcs = [ 66 "main.go", 67 ":gen_go", 68 ], 69 data = [":helper"], 70 embedsrcs = [":helper"], 71 cdeps = [":helper"], 72 cgo = True, 73 linkmode = "c-archive", 74 gotags = ["foo"], 75) 76 77cc_library( 78 name = "helper", 79) 80 81cc_library( 82 name = "indirect_helper", 83) 84 85genrule( 86 name = "gen_go", 87 outs = ["gen.go"], 88 exec_tools = [":helper"], 89 cmd = "# Not needed for bazel cquery", 90) 91 92genrule( 93 name = "gen_indirect_go", 94 outs = ["gen_indirect.go"], 95 exec_tools = [":indirect_helper"], 96 cmd = "# Not needed for bazel cquery", 97) 98 99proto_library( 100 name = "foo_proto", 101 srcs = ["foo.proto"], 102) 103 104go_proto_library( 105 name = "foo_go_proto", 106 importpath = "github.com/bazelbuild/rules_go/tests/core/transition/foo", 107 proto = ":foo_proto", 108) 109-- main.go -- 110package main 111 112func main() {} 113-- lib.go -- 114-- foo.proto -- 115syntax = "proto3"; 116 117package tests.core.transition.foo; 118option go_package = "github.com/bazelbuild/rules_go/tests/core/transition/foo"; 119 120message Foo { 121 int64 value = 1; 122} 123`, 124 WorkspaceSuffix: ` 125load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 126 127http_archive( 128 name = "com_google_protobuf", 129 sha256 = "75be42bd736f4df6d702a0e4e4d30de9ee40eac024c4b845d17ae4cc831fe4ae", 130 strip_prefix = "protobuf-21.7", 131 # latest available in BCR, as of 2022-09-30 132 urls = [ 133 "https://github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz", 134 "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz", 135 ], 136) 137 138load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") 139 140protobuf_deps() 141 142http_archive( 143 name = "rules_proto", 144 sha256 = "4d421d51f9ecfe9bf96ab23b55c6f2b809cbaf0eea24952683e397decfbd0dd0", 145 strip_prefix = "rules_proto-f6b8d89b90a7956f6782a4a3609b2f0eee3ce965", 146 # master, as of 2020-01-06 147 urls = [ 148 "https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/f6b8d89b90a7956f6782a4a3609b2f0eee3ce965.tar.gz", 149 "https://github.com/bazelbuild/rules_proto/archive/f6b8d89b90a7956f6782a4a3609b2f0eee3ce965.tar.gz", 150 ], 151) 152`, 153 }) 154} 155 156func TestGoBinaryNonGoAttrsAreReset(t *testing.T) { 157 assertDependsCleanlyOnWithFlags( 158 t, 159 "//:main", 160 "//:helper") 161} 162 163func TestGoLibraryNonGoAttrsAreReset(t *testing.T) { 164 assertDependsCleanlyOnWithFlags( 165 t, 166 "//:main", 167 "//:indirect_helper") 168} 169 170func TestGoTestNonGoAttrsAreReset(t *testing.T) { 171 assertDependsCleanlyOnWithFlags( 172 t, 173 "//:main_test", 174 "//:helper") 175} 176 177func TestGoProtoLibraryToolAttrsAreReset(t *testing.T) { 178 assertDependsCleanlyOnWithFlags( 179 t, 180 "//:foo_go_proto", 181 "@com_google_protobuf//:protoc", 182 "--@io_bazel_rules_go//go/config:static", 183 "--@io_bazel_rules_go//go/config:msan", 184 "--@io_bazel_rules_go//go/config:race", 185 "--@io_bazel_rules_go//go/config:debug", 186 "--@io_bazel_rules_go//go/config:linkmode=c-archive", 187 "--@io_bazel_rules_go//go/config:tags=fake_tag", 188 ) 189 assertDependsCleanlyOnWithFlags( 190 t, 191 "//:foo_go_proto", 192 "@com_google_protobuf//:protoc", 193 "--@io_bazel_rules_go//go/config:pure", 194 ) 195} 196 197func assertDependsCleanlyOnWithFlags(t *testing.T, targetA, targetB string, flags ...string) { 198 query := fmt.Sprintf("deps(%s) intersect %s", targetA, targetB) 199 out, err := bazel_testing.BazelOutput(append( 200 []string{ 201 "cquery", 202 "--transitions=full", 203 "--output=jsonproto", 204 query, 205 }, 206 flags..., 207 )..., 208 ) 209 if err != nil { 210 t.Fatalf("bazel cquery '%s': %v", query, err) 211 } 212 cqueryOut := bytes.TrimSpace(out) 213 configHashes := extractConfigHashes(t, cqueryOut) 214 if len(configHashes) != 1 { 215 differingGoOptions := getGoOptions(t, configHashes...) 216 if len(differingGoOptions) != 0 { 217 t.Fatalf( 218 "%s depends on %s in multiple configs with these differences in rules_go options: %s", 219 targetA, 220 targetB, 221 strings.Join(differingGoOptions, "\n"), 222 ) 223 } 224 } 225 goOptions := getGoOptions(t, configHashes[0]) 226 if len(goOptions) != 0 { 227 t.Fatalf( 228 "%s depends on %s in a config with rules_go options: %s", 229 targetA, 230 targetB, 231 strings.Join(goOptions, "\n"), 232 ) 233 } 234} 235 236func extractConfigHashes(t *testing.T, rawJsonOut []byte) []string { 237 var jsonOut bazelCqueryOutput 238 err := json.Unmarshal(rawJsonOut, &jsonOut) 239 if err != nil { 240 t.Fatalf("Failed to decode bazel config JSON output %v: %q", err, string(rawJsonOut)) 241 } 242 var hashes []string 243 for _, result := range jsonOut.Results { 244 hashes = append(hashes, result.Configuration.Checksum) 245 } 246 return hashes 247} 248 249func getGoOptions(t *testing.T, hashes ...string) []string { 250 out, err := bazel_testing.BazelOutput(append([]string{"config", "--output=json"}, hashes...)...) 251 if err != nil { 252 t.Fatalf("bazel config %s: %v", strings.Join(hashes, " "), err) 253 } 254 rawJsonOut := bytes.TrimSpace(out) 255 var jsonOut bazelConfigOutput 256 err = json.Unmarshal(rawJsonOut, &jsonOut) 257 if err != nil { 258 t.Fatalf("Failed to decode bazel config JSON output %v: %q", err, string(rawJsonOut)) 259 } 260 var differingGoOptions []string 261 for _, fragment := range jsonOut.Fragments { 262 if fragment.Name != starlarkOptionsFragment { 263 continue 264 } 265 for key, value := range fragment.Options { 266 if strings.HasPrefix(key, "@io_bazel_rules_go//") { 267 differingGoOptions = append(differingGoOptions, fmt.Sprintf("%s=%s", key, value)) 268 } 269 } 270 } 271 return differingGoOptions 272} 273 274const starlarkOptionsFragment = "user-defined" 275 276type bazelConfigOutput struct { 277 Fragments []struct { 278 Name string `json:"name"` 279 Options map[string]string `json:"options"` 280 } `json:"fragmentOptions"` 281} 282 283type bazelCqueryOutput struct { 284 Results []struct { 285 Configuration struct { 286 Checksum string `json:"checksum"` 287 } `json:"configuration"` 288 } `json:"results"` 289} 290