blob: 255b2e537d78cbf6d05e897973c39a7a17245f57 [file] [log] [blame]
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +02001// 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 rust
16
17import (
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +020018 "encoding/json"
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +020019 "io/ioutil"
20 "path/filepath"
Thiébaud Weksteene8b0ee72021-03-25 09:26:07 +010021 "sort"
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +020022 "strings"
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +020023 "testing"
24
25 "android/soong/android"
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +020026)
27
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +020028// testProjectJson run the generation of rust-project.json. It returns the raw
29// content of the generated file.
Thiébaud Weksteen0a75e522020-10-07 14:30:03 +020030func testProjectJson(t *testing.T, bp string) []byte {
Paul Duffin79abe572021-03-29 02:16:14 +010031 result := android.GroupFixturePreparers(
32 prepareForRustTest,
33 android.FixtureMergeEnv(map[string]string{"SOONG_GEN_RUST_PROJECT": "1"}),
34 ).RunTestWithBp(t, bp)
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +020035
36 // The JSON file is generated via WriteFileToOutputDir. Therefore, it
37 // won't appear in the Output of the TestingSingleton. Manually verify
38 // it exists.
Lukacs T. Berki9f6c24a2021-08-26 15:07:24 +020039 content, err := ioutil.ReadFile(filepath.Join(result.Config.SoongOutDir(), rustProjectJsonFileName))
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +020040 if err != nil {
41 t.Errorf("rust-project.json has not been generated")
42 }
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +020043 return content
44}
45
46// validateJsonCrates validates that content follows the basic structure of
47// rust-project.json. It returns the crates attribute if the validation
48// succeeded.
49// It uses an empty interface instead of relying on a defined structure to
50// avoid a strong dependency on our implementation.
51func validateJsonCrates(t *testing.T, rawContent []byte) []interface{} {
52 var content interface{}
53 err := json.Unmarshal(rawContent, &content)
54 if err != nil {
55 t.Errorf("Unable to parse the rust-project.json as JSON: %v", err)
56 }
57 root, ok := content.(map[string]interface{})
58 if !ok {
59 t.Errorf("Unexpected JSON format: %v", content)
60 }
61 if _, ok = root["crates"]; !ok {
62 t.Errorf("No crates attribute in rust-project.json: %v", root)
63 }
64 crates, ok := root["crates"].([]interface{})
65 if !ok {
66 t.Errorf("Unexpected crates format: %v", root["crates"])
67 }
68 return crates
69}
70
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +010071// validateCrate ensures that a crate can be parsed as a map.
72func validateCrate(t *testing.T, crate interface{}) map[string]interface{} {
73 c, ok := crate.(map[string]interface{})
74 if !ok {
75 t.Fatalf("Unexpected type for crate: %v", c)
76 }
77 return c
78}
79
80// validateDependencies parses the dependencies for a crate. It returns a list
81// of the dependencies name.
82func validateDependencies(t *testing.T, crate map[string]interface{}) []string {
83 var dependencies []string
84 deps, ok := crate["deps"].([]interface{})
85 if !ok {
86 t.Errorf("Unexpected format for deps: %v", crate["deps"])
87 }
88 for _, dep := range deps {
89 d, ok := dep.(map[string]interface{})
90 if !ok {
91 t.Errorf("Unexpected format for dependency: %v", dep)
92 }
93 name, ok := d["name"].(string)
94 if !ok {
95 t.Errorf("Dependency is missing the name key: %v", d)
96 }
97 dependencies = append(dependencies, name)
98 }
99 return dependencies
100}
101
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200102func TestProjectJsonDep(t *testing.T) {
103 bp := `
104 rust_library {
105 name: "liba",
106 srcs: ["a/src/lib.rs"],
107 crate_name: "a"
108 }
109 rust_library {
110 name: "libb",
111 srcs: ["b/src/lib.rs"],
112 crate_name: "b",
113 rlibs: ["liba"],
114 }
Thiébaud Weksteen0a75e522020-10-07 14:30:03 +0200115 `
116 jsonContent := testProjectJson(t, bp)
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200117 validateJsonCrates(t, jsonContent)
118}
119
Seth Mooreaf96f992021-10-06 10:45:34 -0700120func TestProjectJsonProcMacroDep(t *testing.T) {
121 bp := `
122 rust_proc_macro {
123 name: "libproc_macro",
124 srcs: ["a/src/lib.rs"],
125 crate_name: "proc_macro"
126 }
127 rust_library {
128 name: "librust",
129 srcs: ["b/src/lib.rs"],
130 crate_name: "rust",
131 proc_macros: ["libproc_macro"],
132 }
133 `
134 jsonContent := testProjectJson(t, bp)
135 crates := validateJsonCrates(t, jsonContent)
136 libproc_macro_count := 0
137 librust_count := 0
138 for _, c := range crates {
139 crate := validateCrate(t, c)
140 procMacro, ok := crate["is_proc_macro"].(bool)
141 if !ok {
142 t.Fatalf("Unexpected type for is_proc_macro: %v", crate["is_proc_macro"])
143 }
144
145 name, ok := crate["display_name"].(string)
146 if !ok {
147 t.Fatalf("Unexpected type for display_name: %v", crate["display_name"])
148 }
149
150 switch name {
151 case "libproc_macro":
152 libproc_macro_count += 1
153 if !procMacro {
154 t.Fatalf("'libproc_macro' is marked with is_proc_macro=false")
155 }
156 case "librust":
157 librust_count += 1
158 if procMacro {
159 t.Fatalf("'librust' is not a proc macro crate, but is marked with is_proc_macro=true")
160 }
161 default:
162 break
163 }
164 }
165
166 if libproc_macro_count != 1 || librust_count != 1 {
167 t.Fatalf("Unexpected crate counts: libproc_macro_count: %v, librust_count: %v",
168 libproc_macro_count, librust_count)
169 }
170}
171
Thiébaud Weksteene8b0ee72021-03-25 09:26:07 +0100172func TestProjectJsonFeature(t *testing.T) {
173 bp := `
174 rust_library {
175 name: "liba",
176 srcs: ["a/src/lib.rs"],
177 crate_name: "a",
178 features: ["f1", "f2"]
179 }
180 `
181 jsonContent := testProjectJson(t, bp)
182 crates := validateJsonCrates(t, jsonContent)
183 for _, c := range crates {
184 crate := validateCrate(t, c)
185 cfgs, ok := crate["cfg"].([]interface{})
186 if !ok {
187 t.Fatalf("Unexpected type for cfgs: %v", crate)
188 }
189 expectedCfgs := []string{"feature=\"f1\"", "feature=\"f2\""}
190 foundCfgs := []string{}
191 for _, cfg := range cfgs {
192 cfg, ok := cfg.(string)
193 if !ok {
194 t.Fatalf("Unexpected type for cfg: %v", cfg)
195 }
196 foundCfgs = append(foundCfgs, cfg)
197 }
198 sort.Strings(foundCfgs)
199 for i, foundCfg := range foundCfgs {
200 if foundCfg != expectedCfgs[i] {
201 t.Errorf("Incorrect features: got %v; want %v", foundCfg, expectedCfgs[i])
202 }
203 }
204 }
205}
206
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100207func TestProjectJsonBinary(t *testing.T) {
208 bp := `
209 rust_binary {
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100210 name: "libz",
211 srcs: ["z/src/lib.rs"],
212 crate_name: "z"
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100213 }
214 `
215 jsonContent := testProjectJson(t, bp)
216 crates := validateJsonCrates(t, jsonContent)
217 for _, c := range crates {
218 crate := validateCrate(t, c)
219 rootModule, ok := crate["root_module"].(string)
220 if !ok {
221 t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
222 }
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100223 if rootModule == "z/src/lib.rs" {
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100224 return
225 }
226 }
227 t.Errorf("Entry for binary %q not found: %s", "a", jsonContent)
228}
229
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200230func TestProjectJsonBindGen(t *testing.T) {
Colin Cross0c66bc62021-07-20 09:47:41 -0700231 buildOS := android.TestConfig(t.TempDir(), nil, "", nil).BuildOS
232
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200233 bp := `
234 rust_library {
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100235 name: "libd",
236 srcs: ["d/src/lib.rs"],
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200237 rlibs: ["libbindings1"],
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100238 crate_name: "d"
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200239 }
240 rust_bindgen {
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200241 name: "libbindings1",
242 crate_name: "bindings1",
243 source_stem: "bindings1",
Thiébaud Weksteen83ee52f2020-08-05 09:29:23 +0200244 host_supported: true,
245 wrapper_src: "src/any.h",
246 }
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200247 rust_library_host {
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100248 name: "libe",
249 srcs: ["e/src/lib.rs"],
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200250 rustlibs: ["libbindings2"],
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100251 crate_name: "e"
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200252 }
253 rust_bindgen_host {
254 name: "libbindings2",
255 crate_name: "bindings2",
256 source_stem: "bindings2",
257 wrapper_src: "src/any.h",
258 }
Thiébaud Weksteen0a75e522020-10-07 14:30:03 +0200259 `
260 jsonContent := testProjectJson(t, bp)
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200261 crates := validateJsonCrates(t, jsonContent)
262 for _, c := range crates {
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100263 crate := validateCrate(t, c)
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200264 rootModule, ok := crate["root_module"].(string)
265 if !ok {
266 t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
267 }
268 if strings.Contains(rootModule, "libbindings1") && !strings.Contains(rootModule, "android_arm64") {
Thiébaud Weksteen4f6c7662020-09-30 13:33:41 +0200269 t.Errorf("The source path for libbindings1 does not contain android_arm64, got %v", rootModule)
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200270 }
Colin Cross0c66bc62021-07-20 09:47:41 -0700271 if strings.Contains(rootModule, "libbindings2") && !strings.Contains(rootModule, buildOS.String()) {
Thiébaud Weksteen4f6c7662020-09-30 13:33:41 +0200272 t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
Colin Cross0c66bc62021-07-20 09:47:41 -0700273 rootModule, buildOS.String())
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200274 }
Thiébaud Weksteen3c5905b2020-11-25 16:09:32 +0100275 // Check that libbindings1 does not depend on itself.
276 if strings.Contains(rootModule, "libbindings1") {
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100277 for _, depName := range validateDependencies(t, crate) {
278 if depName == "bindings1" {
Thiébaud Weksteen3c5905b2020-11-25 16:09:32 +0100279 t.Errorf("libbindings1 depends on itself")
280 }
281 }
282 }
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100283 if strings.Contains(rootModule, "d/src/lib.rs") {
Thiébaud Weksteenee6a89b2021-02-25 16:30:57 +0100284 // Check that libd depends on libbindings1
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100285 found := false
286 for _, depName := range validateDependencies(t, crate) {
287 if depName == "bindings1" {
288 found = true
289 break
290 }
291 }
292 if !found {
Thiébaud Weksteenee6a89b2021-02-25 16:30:57 +0100293 t.Errorf("libd does not depend on libbindings1: %v", crate)
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100294 }
Thiébaud Weksteenee6a89b2021-02-25 16:30:57 +0100295 // Check that OUT_DIR is populated.
296 env, ok := crate["env"].(map[string]interface{})
297 if !ok {
298 t.Errorf("libd does not have its environment variables set: %v", crate)
299 }
300 if _, ok = env["OUT_DIR"]; !ok {
301 t.Errorf("libd does not have its OUT_DIR set: %v", env)
302 }
303
Thiébaud Weksteenfa5feae2020-12-07 13:40:19 +0100304 }
Thiébaud Weksteena6351ca2020-09-29 09:53:21 +0200305 }
Thiébaud Weksteene4d12a02020-06-05 11:09:27 +0200306}
Thiébaud Weksteen2f628ba2020-08-05 14:27:32 +0200307
308func TestProjectJsonMultiVersion(t *testing.T) {
309 bp := `
310 rust_library {
311 name: "liba1",
312 srcs: ["a1/src/lib.rs"],
313 crate_name: "a"
314 }
315 rust_library {
316 name: "liba2",
317 srcs: ["a2/src/lib.rs"],
318 crate_name: "a",
319 }
320 rust_library {
321 name: "libb",
322 srcs: ["b/src/lib.rs"],
323 crate_name: "b",
324 rustlibs: ["liba1", "liba2"],
325 }
Thiébaud Weksteen0a75e522020-10-07 14:30:03 +0200326 `
327 jsonContent := testProjectJson(t, bp)
Thiébaud Weksteen2f628ba2020-08-05 14:27:32 +0200328 crates := validateJsonCrates(t, jsonContent)
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100329 for _, c := range crates {
330 crate := validateCrate(t, c)
331 rootModule, ok := crate["root_module"].(string)
332 if !ok {
333 t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
334 }
335 // Make sure that b has 2 different dependencies.
336 if rootModule == "b/src/lib.rs" {
Thiébaud Weksteen2f628ba2020-08-05 14:27:32 +0200337 aCount := 0
Thiébaud Weksteen064f6e92020-12-03 20:58:32 +0100338 deps := validateDependencies(t, crate)
339 for _, depName := range deps {
340 if depName == "a" {
Thiébaud Weksteen2f628ba2020-08-05 14:27:32 +0200341 aCount++
342 }
343 }
344 if aCount != 2 {
345 t.Errorf("Unexpected number of liba dependencies want %v, got %v: %v", 2, aCount, deps)
346 }
347 return
348 }
349 }
350 t.Errorf("libb crate has not been found: %v", crates)
351}