From 891cd578c70911416775511654ad396fc95be054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Wed, 5 Aug 2020 09:29:23 +0200 Subject: [PATCH 1/2] rust: validate existence of library source While rust_bindgen modules may be interpreted as libraries, they do not have a reference to the generated source until bindgen has been executed. For now, ignore these modules. Update the unit tests to cover the rust_bindgen case. Test: SOONG_GEN_RUST_PROJECT=1 m nothing Bug: 162881856 Change-Id: Ie736d7ebb115f2c1f9a90be006c972ce24265c6a --- rust/project_json.go | 8 ++-- rust/project_json_test.go | 90 +++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/rust/project_json.go b/rust/project_json.go index 41dd194ee..75375408c 100644 --- a/rust/project_json.go +++ b/rust/project_json.go @@ -75,7 +75,6 @@ func mergeDependencies(ctx android.SingletonContext, project *rustProjectJson, knownCrates map[string]crateInfo, module android.Module, crate *rustProjectCrate, deps map[string]int) { - //TODO(tweek): The stdlib dependencies do not appear here. We need to manually add them. ctx.VisitDirectDeps(module, func(child android.Module) { childId, childName, ok := appendLibraryAndDeps(ctx, project, knownCrates, child) if !ok { @@ -115,8 +114,11 @@ func appendLibraryAndDeps(ctx android.SingletonContext, project *rustProjectJson return cInfo.ID, crateName, true } crate := rustProjectCrate{Deps: make([]rustProjectDep, 0), Cfgs: make([]string, 0)} - src := rustLib.baseCompiler.Properties.Srcs[0] - crate.RootModule = path.Join(ctx.ModuleDir(rModule), src) + srcs := rustLib.baseCompiler.Properties.Srcs + if len(srcs) == 0 { + return 0, "", false + } + crate.RootModule = path.Join(ctx.ModuleDir(rModule), srcs[0]) crate.Edition = rustLib.baseCompiler.edition() deps := make(map[string]int) diff --git a/rust/project_json_test.go b/rust/project_json_test.go index 6786e72c7..89ba8d352 100644 --- a/rust/project_json_test.go +++ b/rust/project_json_test.go @@ -15,6 +15,7 @@ package rust import ( + "encoding/json" "io/ioutil" "path/filepath" "testing" @@ -23,20 +24,12 @@ import ( "android/soong/cc" ) -func TestProjectJson(t *testing.T) { - bp := `rust_library { - name: "liba", - srcs: ["src/lib.rs"], - crate_name: "a" - }` + GatherRequiredDepsForTest() - env := map[string]string{"SOONG_GEN_RUST_PROJECT": "1"} - fs := map[string][]byte{ - "foo.rs": nil, - "src/lib.rs": nil, - } - +// testProjectJson run the generation of rust-project.json. It returns the raw +// content of the generated file. +func testProjectJson(t *testing.T, bp string, fs map[string][]byte) []byte { cc.GatherRequiredFilesForTest(fs) + env := map[string]string{"SOONG_GEN_RUST_PROJECT": "1"} config := android.TestArchConfig(buildDir, env, bp, fs) ctx := CreateTestContext() ctx.Register(config) @@ -48,8 +41,79 @@ func TestProjectJson(t *testing.T) { // The JSON file is generated via WriteFileToOutputDir. Therefore, it // won't appear in the Output of the TestingSingleton. Manually verify // it exists. - _, err := ioutil.ReadFile(filepath.Join(buildDir, "rust-project.json")) + content, err := ioutil.ReadFile(filepath.Join(buildDir, rustProjectJsonFileName)) if err != nil { t.Errorf("rust-project.json has not been generated") } + return content +} + +// validateJsonCrates validates that content follows the basic structure of +// rust-project.json. It returns the crates attribute if the validation +// succeeded. +// It uses an empty interface instead of relying on a defined structure to +// avoid a strong dependency on our implementation. +func validateJsonCrates(t *testing.T, rawContent []byte) []interface{} { + var content interface{} + err := json.Unmarshal(rawContent, &content) + if err != nil { + t.Errorf("Unable to parse the rust-project.json as JSON: %v", err) + } + root, ok := content.(map[string]interface{}) + if !ok { + t.Errorf("Unexpected JSON format: %v", content) + } + if _, ok = root["crates"]; !ok { + t.Errorf("No crates attribute in rust-project.json: %v", root) + } + crates, ok := root["crates"].([]interface{}) + if !ok { + t.Errorf("Unexpected crates format: %v", root["crates"]) + } + return crates +} + +func TestProjectJsonDep(t *testing.T) { + bp := ` + rust_library { + name: "liba", + srcs: ["a/src/lib.rs"], + crate_name: "a" + } + rust_library { + name: "libb", + srcs: ["b/src/lib.rs"], + crate_name: "b", + rlibs: ["liba"], + } + ` + GatherRequiredDepsForTest() + fs := map[string][]byte{ + "a/src/lib.rs": nil, + "b/src/lib.rs": nil, + } + jsonContent := testProjectJson(t, bp, fs) + validateJsonCrates(t, jsonContent) +} + +func TestProjectJsonBindGen(t *testing.T) { + bp := ` + rust_library { + name: "liba", + srcs: ["src/lib.rs"], + rlibs: ["libbindings"], + crate_name: "a" + } + rust_bindgen { + name: "libbindings", + crate_name: "bindings", + source_stem: "bindings", + host_supported: true, + wrapper_src: "src/any.h", + } + ` + GatherRequiredDepsForTest() + fs := map[string][]byte{ + "src/lib.rs": nil, + } + jsonContent := testProjectJson(t, bp, fs) + validateJsonCrates(t, jsonContent) } From ad8ac5358182e72bb4ede89e9a98e2b23b03523b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Wed, 5 Aug 2020 14:27:32 +0200 Subject: [PATCH 2/2] rust: handle modules with same crate_name crate_name do not need to be unique. A library may depend on two different versions of the same crate. Use the module name instead of the crate name when indexing the modules for rust-project.json. Test: SOONG_GEN_RUST_PROJECT=1 m nothing Bug: 162896400 Change-Id: I275f04639ef05f2b649c30168046e13b2efcacb9 --- rust/project_json.go | 13 +++++----- rust/project_json_test.go | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/rust/project_json.go b/rust/project_json.go index 75375408c..831047938 100644 --- a/rust/project_json.go +++ b/rust/project_json.go @@ -76,15 +76,15 @@ func mergeDependencies(ctx android.SingletonContext, project *rustProjectJson, crate *rustProjectCrate, deps map[string]int) { ctx.VisitDirectDeps(module, func(child android.Module) { - childId, childName, ok := appendLibraryAndDeps(ctx, project, knownCrates, child) + childId, childCrateName, ok := appendLibraryAndDeps(ctx, project, knownCrates, child) if !ok { return } - if _, ok = deps[childName]; ok { + if _, ok = deps[ctx.ModuleName(child)]; ok { return } - crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: childName}) - deps[childName] = childId + crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: childCrateName}) + deps[ctx.ModuleName(child)] = childId }) } @@ -105,8 +105,9 @@ func appendLibraryAndDeps(ctx android.SingletonContext, project *rustProjectJson if !ok { return 0, "", false } + moduleName := ctx.ModuleName(module) crateName := rModule.CrateName() - if cInfo, ok := knownCrates[crateName]; ok { + if cInfo, ok := knownCrates[moduleName]; ok { // We have seen this crate already; merge any new dependencies. crate := project.Crates[cInfo.ID] mergeDependencies(ctx, project, knownCrates, module, &crate, cInfo.Deps) @@ -125,7 +126,7 @@ func appendLibraryAndDeps(ctx android.SingletonContext, project *rustProjectJson mergeDependencies(ctx, project, knownCrates, module, &crate, deps) id := len(project.Crates) - knownCrates[crateName] = crateInfo{ID: id, Deps: deps} + knownCrates[moduleName] = crateInfo{ID: id, Deps: deps} project.Crates = append(project.Crates, crate) // rust-analyzer requires that all crates belong to at least one root: // https://github.com/rust-analyzer/rust-analyzer/issues/4735. diff --git a/rust/project_json_test.go b/rust/project_json_test.go index 89ba8d352..85219404a 100644 --- a/rust/project_json_test.go +++ b/rust/project_json_test.go @@ -117,3 +117,55 @@ func TestProjectJsonBindGen(t *testing.T) { jsonContent := testProjectJson(t, bp, fs) validateJsonCrates(t, jsonContent) } + +func TestProjectJsonMultiVersion(t *testing.T) { + bp := ` + rust_library { + name: "liba1", + srcs: ["a1/src/lib.rs"], + crate_name: "a" + } + rust_library { + name: "liba2", + srcs: ["a2/src/lib.rs"], + crate_name: "a", + } + rust_library { + name: "libb", + srcs: ["b/src/lib.rs"], + crate_name: "b", + rustlibs: ["liba1", "liba2"], + } + ` + GatherRequiredDepsForTest() + fs := map[string][]byte{ + "a1/src/lib.rs": nil, + "a2/src/lib.rs": nil, + "b/src/lib.rs": nil, + } + jsonContent := testProjectJson(t, bp, fs) + crates := validateJsonCrates(t, jsonContent) + for _, crate := range crates { + c := crate.(map[string]interface{}) + if c["root_module"] == "b/src/lib.rs" { + deps, ok := c["deps"].([]interface{}) + if !ok { + t.Errorf("Unexpected format for deps: %v", c["deps"]) + } + aCount := 0 + for _, dep := range deps { + d, ok := dep.(map[string]interface{}) + if !ok { + t.Errorf("Unexpected format for dep: %v", dep) + } + if d["name"] == "a" { + aCount++ + } + } + if aCount != 2 { + t.Errorf("Unexpected number of liba dependencies want %v, got %v: %v", 2, aCount, deps) + } + return + } + } + t.Errorf("libb crate has not been found: %v", crates) +}