From fa5feae43c3102ce62efcb6fe305e37b523d269b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Mon, 7 Dec 2020 13:40:19 +0100 Subject: [PATCH] Add dependencies for source-generated crates When using SourceProviders, the dependency tree does not include directly the source variant, only the built variant. For instance: liba --> libbingena_rlib --> libbingena_source However, libbindgena_rlib did not have a source associated with the module, and was therefore not added as a dependency. Modify the logic so that a SourceProvider library will find the right variant and always have a source defined. Adds display_name fields to the crate description to ease debugging. Test: rust-analyzer analysis-stats . Bug: 174158339 Change-Id: Id65708d57cd176f7e1da353f4a5f7ad65b003090 --- rust/project_json.go | 212 +++++++++++++++++++++++++++----------- rust/project_json_test.go | 33 ++++-- 2 files changed, 172 insertions(+), 73 deletions(-) diff --git a/rust/project_json.go b/rust/project_json.go index c4d60ad53..32ce6f4b0 100644 --- a/rust/project_json.go +++ b/rust/project_json.go @@ -45,10 +45,11 @@ type rustProjectDep struct { } type rustProjectCrate struct { - RootModule string `json:"root_module"` - Edition string `json:"edition,omitempty"` - Deps []rustProjectDep `json:"deps"` - Cfgs []string `json:"cfgs"` + DisplayName string `json:"display_name"` + RootModule string `json:"root_module"` + Edition string `json:"edition,omitempty"` + Deps []rustProjectDep `json:"deps"` + Cfgs []string `json:"cfgs"` } type rustProjectJson struct { @@ -58,13 +59,13 @@ type rustProjectJson struct { // crateInfo is used during the processing to keep track of the known crates. type crateInfo struct { - ID int - Deps map[string]int + Idx int // Index of the crate in rustProjectJson.Crates slice. + Deps map[string]int // The keys are the module names and not the crate names. } type projectGeneratorSingleton struct { project rustProjectJson - knownCrates map[string]crateInfo + knownCrates map[string]crateInfo // Keys are module names. } func rustProjectGeneratorSingleton() android.Singleton { @@ -75,66 +76,129 @@ func init() { android.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton) } -// crateSource finds the main source file (.rs) for a crate. -func crateSource(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (string, bool) { - srcs := comp.Properties.Srcs - if len(srcs) != 0 { - return path.Join(ctx.ModuleDir(rModule), srcs[0]), true - } +// sourceProviderVariantSource returns the path to the source file if this +// module variant should be used as a priority. +// +// SourceProvider modules may have multiple variants considered as source +// (e.g., x86_64 and armv8). For a module available on device, use the source +// generated for the target. For a host-only module, use the source generated +// for the host. +func sourceProviderVariantSource(ctx android.SingletonContext, rModule *Module) (string, bool) { rustLib, ok := rModule.compiler.(*libraryDecorator) if !ok { return "", false } - if !rustLib.source() { - return "", false - } - // It is a SourceProvider module. If this module is host only, uses the variation for the host. - // Otherwise, use the variation for the primary target. - switch rModule.hod { - case android.HostSupported: - case android.HostSupportedNoCross: - if rModule.Target().String() != ctx.Config().BuildOSTarget.String() { - return "", false - } - default: - if rModule.Target().String() != ctx.Config().AndroidFirstDeviceTarget.String() { - return "", false + if rustLib.source() { + switch rModule.hod { + case android.HostSupported, android.HostSupportedNoCross: + if rModule.Target().String() == ctx.Config().BuildOSTarget.String() { + src := rustLib.sourceProvider.Srcs()[0] + return src.String(), true + } + default: + if rModule.Target().String() == ctx.Config().AndroidFirstDeviceTarget.String() { + src := rustLib.sourceProvider.Srcs()[0] + return src.String(), true + } } } - src := rustLib.sourceProvider.Srcs()[0] - return src.String(), true + return "", false } -func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext, - module android.Module, crate *rustProjectCrate, deps map[string]int) { - - ctx.VisitDirectDeps(module, func(child android.Module) { - childId, childCrateName, ok := singleton.appendLibraryAndDeps(ctx, child) - if !ok { +// sourceProviderSource finds the main source file of a source-provider crate. +func sourceProviderSource(ctx android.SingletonContext, rModule *Module) (string, bool) { + rustLib, ok := rModule.compiler.(*libraryDecorator) + if !ok { + return "", false + } + if rustLib.source() { + // This is a source-variant, check if we are the right variant + // depending on the module configuration. + if src, ok := sourceProviderVariantSource(ctx, rModule); ok { + return src, true + } + } + foundSource := false + sourceSrc := "" + // Find the variant with the source and return its. + ctx.VisitAllModuleVariants(rModule, func(variant android.Module) { + if foundSource { return } + // All variants of a source provider library are libraries. + rVariant, _ := variant.(*Module) + variantLib, _ := rVariant.compiler.(*libraryDecorator) + if variantLib.source() { + sourceSrc, ok = sourceProviderVariantSource(ctx, rVariant) + if ok { + foundSource = true + } + } + }) + if !foundSource { + fmt.Errorf("No valid source for source provider found: %v\n", rModule) + } + return sourceSrc, foundSource +} + +// crateSource finds the main source file (.rs) for a crate. +func crateSource(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (string, bool) { + // Basic libraries, executables and tests. + srcs := comp.Properties.Srcs + if len(srcs) != 0 { + return path.Join(ctx.ModuleDir(rModule), srcs[0]), true + } + // SourceProvider libraries. + if rModule.sourceProvider != nil { + return sourceProviderSource(ctx, rModule) + } + return "", false +} + +// mergeDependencies visits all the dependencies for module and updates crate and deps +// with any new dependency. +func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext, + module *Module, crate *rustProjectCrate, deps map[string]int) { + + ctx.VisitDirectDeps(module, func(child android.Module) { // Skip intra-module dependencies (i.e., generated-source library depending on the source variant). if module.Name() == child.Name() { return } - if _, ok = deps[ctx.ModuleName(child)]; ok { + // Skip unsupported modules. + rChild, compChild, ok := isModuleSupported(ctx, child) + if !ok { return } - crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: childCrateName}) - deps[ctx.ModuleName(child)] = childId + // For unknown dependency, add it first. + var childId int + cInfo, known := singleton.knownCrates[rChild.Name()] + if !known { + childId, ok = singleton.addCrate(ctx, rChild, compChild) + if !ok { + return + } + } else { + childId = cInfo.Idx + } + // Is this dependency known already? + if _, ok = deps[child.Name()]; ok { + return + } + crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: rChild.CrateName()}) + deps[child.Name()] = childId }) } -// appendLibraryAndDeps creates a rustProjectCrate for the module argument and appends it to singleton.project. -// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the -// current module is already in singleton.knownCrates, its dependencies are merged. Returns a tuple (id, crate_name, ok). -func (singleton *projectGeneratorSingleton) appendLibraryAndDeps(ctx android.SingletonContext, module android.Module) (int, string, bool) { +// isModuleSupported returns the RustModule and baseCompiler if the module +// should be considered for inclusion in rust-project.json. +func isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, *baseCompiler, bool) { rModule, ok := module.(*Module) if !ok { - return 0, "", false + return nil, nil, false } if rModule.compiler == nil { - return 0, "", false + return nil, nil, false } var comp *baseCompiler switch c := rModule.compiler.(type) { @@ -145,35 +209,57 @@ func (singleton *projectGeneratorSingleton) appendLibraryAndDeps(ctx android.Sin case *testDecorator: comp = c.binaryDecorator.baseCompiler default: - return 0, "", false + return nil, nil, false } - moduleName := ctx.ModuleName(module) - crateName := rModule.CrateName() - if cInfo, ok := singleton.knownCrates[moduleName]; ok { - // We have seen this crate already; merge any new dependencies. - crate := singleton.project.Crates[cInfo.ID] - singleton.mergeDependencies(ctx, module, &crate, cInfo.Deps) - singleton.project.Crates[cInfo.ID] = crate - return cInfo.ID, crateName, true - } - crate := rustProjectCrate{Deps: make([]rustProjectDep, 0), Cfgs: make([]string, 0)} + return rModule, comp, true +} + +// addCrate adds a crate to singleton.project.Crates ensuring that required +// dependencies are also added. It returns the index of the new crate in +// singleton.project.Crates +func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (int, bool) { rootModule, ok := crateSource(ctx, rModule, comp) if !ok { - return 0, "", false + fmt.Errorf("Unable to find source for valid module: %v", rModule) + return 0, false + } + + crate := rustProjectCrate{ + DisplayName: rModule.Name(), + RootModule: rootModule, + Edition: comp.edition(), + Deps: make([]rustProjectDep, 0), + Cfgs: make([]string, 0), } - crate.RootModule = rootModule - crate.Edition = comp.edition() deps := make(map[string]int) - singleton.mergeDependencies(ctx, module, &crate, deps) + singleton.mergeDependencies(ctx, rModule, &crate, deps) - id := len(singleton.project.Crates) - singleton.knownCrates[moduleName] = crateInfo{ID: id, Deps: deps} + idx := len(singleton.project.Crates) + singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps} singleton.project.Crates = append(singleton.project.Crates, crate) // rust-analyzer requires that all crates belong to at least one root: // https://github.com/rust-analyzer/rust-analyzer/issues/4735. singleton.project.Roots = append(singleton.project.Roots, path.Dir(crate.RootModule)) - return id, crateName, true + return idx, true +} + +// appendCrateAndDependencies creates a rustProjectCrate for the module argument and appends it to singleton.project. +// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the +// current module is already in singleton.knownCrates, its dependencies are merged. +func (singleton *projectGeneratorSingleton) appendCrateAndDependencies(ctx android.SingletonContext, module android.Module) { + rModule, comp, ok := isModuleSupported(ctx, module) + if !ok { + return + } + // If we have seen this crate already; merge any new dependencies. + if cInfo, ok := singleton.knownCrates[module.Name()]; ok { + crate := singleton.project.Crates[cInfo.Idx] + singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps) + singleton.project.Crates[cInfo.Idx] = crate + return + } + singleton.addCrate(ctx, rModule, comp) } func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { @@ -183,7 +269,7 @@ func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.Sin singleton.knownCrates = make(map[string]crateInfo) ctx.VisitAllModules(func(module android.Module) { - singleton.appendLibraryAndDeps(ctx, module) + singleton.appendCrateAndDependencies(ctx, module) }) path := android.PathForOutput(ctx, rustProjectJsonFileName) diff --git a/rust/project_json_test.go b/rust/project_json_test.go index aff16978b..ba66215c5 100644 --- a/rust/project_json_test.go +++ b/rust/project_json_test.go @@ -119,9 +119,9 @@ func TestProjectJsonDep(t *testing.T) { func TestProjectJsonBinary(t *testing.T) { bp := ` rust_binary { - name: "liba", - srcs: ["a/src/lib.rs"], - crate_name: "a" + name: "libz", + srcs: ["z/src/lib.rs"], + crate_name: "z" } ` jsonContent := testProjectJson(t, bp) @@ -132,7 +132,7 @@ func TestProjectJsonBinary(t *testing.T) { if !ok { t.Fatalf("Unexpected type for root_module: %v", crate["root_module"]) } - if rootModule == "a/src/lib.rs" { + if rootModule == "z/src/lib.rs" { return } } @@ -142,10 +142,10 @@ func TestProjectJsonBinary(t *testing.T) { func TestProjectJsonBindGen(t *testing.T) { bp := ` rust_library { - name: "liba", - srcs: ["src/lib.rs"], + name: "libd", + srcs: ["d/src/lib.rs"], rlibs: ["libbindings1"], - crate_name: "a" + crate_name: "d" } rust_bindgen { name: "libbindings1", @@ -155,10 +155,10 @@ func TestProjectJsonBindGen(t *testing.T) { wrapper_src: "src/any.h", } rust_library_host { - name: "libb", - srcs: ["src/lib.rs"], + name: "libe", + srcs: ["e/src/lib.rs"], rustlibs: ["libbindings2"], - crate_name: "b" + crate_name: "e" } rust_bindgen_host { name: "libbindings2", @@ -190,6 +190,19 @@ func TestProjectJsonBindGen(t *testing.T) { } } } + // Check that liba depends on libbindings1 + if strings.Contains(rootModule, "d/src/lib.rs") { + found := false + for _, depName := range validateDependencies(t, crate) { + if depName == "bindings1" { + found = true + break + } + } + if !found { + t.Errorf("liba does not depend on libbindings1: %v", crate) + } + } } }