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 rust 16 17import ( 18 "encoding/json" 19 "fmt" 20 "path" 21 22 "android/soong/android" 23) 24 25// This singleton collects Rust crate definitions and generates a JSON file 26// (${OUT_DIR}/soong/rust-project.json) which can be use by external tools, 27// such as rust-analyzer. It does so when either make, mm, mma, mmm or mmma is 28// called. This singleton is enabled only if SOONG_GEN_RUST_PROJECT is set. 29// For example, 30// 31// $ SOONG_GEN_RUST_PROJECT=1 m nothing 32 33const ( 34 // Environment variables used to control the behavior of this singleton. 35 envVariableCollectRustDeps = "SOONG_GEN_RUST_PROJECT" 36 rustProjectJsonFileName = "rust-project.json" 37) 38 39// The format of rust-project.json is not yet finalized. A current description is available at: 40// https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc#non-cargo-based-projects 41type rustProjectDep struct { 42 // The Crate attribute is the index of the dependency in the Crates array in rustProjectJson. 43 Crate int `json:"crate"` 44 Name string `json:"name"` 45} 46 47type rustProjectCrate struct { 48 DisplayName string `json:"display_name"` 49 RootModule string `json:"root_module"` 50 Edition string `json:"edition,omitempty"` 51 Deps []rustProjectDep `json:"deps"` 52 Cfg []string `json:"cfg"` 53 Env map[string]string `json:"env"` 54} 55 56type rustProjectJson struct { 57 Roots []string `json:"roots"` 58 Crates []rustProjectCrate `json:"crates"` 59} 60 61// crateInfo is used during the processing to keep track of the known crates. 62type crateInfo struct { 63 Idx int // Index of the crate in rustProjectJson.Crates slice. 64 Deps map[string]int // The keys are the module names and not the crate names. 65} 66 67type projectGeneratorSingleton struct { 68 project rustProjectJson 69 knownCrates map[string]crateInfo // Keys are module names. 70} 71 72func rustProjectGeneratorSingleton() android.Singleton { 73 return &projectGeneratorSingleton{} 74} 75 76func init() { 77 android.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton) 78} 79 80// sourceProviderVariantSource returns the path to the source file if this 81// module variant should be used as a priority. 82// 83// SourceProvider modules may have multiple variants considered as source 84// (e.g., x86_64 and armv8). For a module available on device, use the source 85// generated for the target. For a host-only module, use the source generated 86// for the host. 87func sourceProviderVariantSource(ctx android.SingletonContext, rModule *Module) (string, bool) { 88 rustLib, ok := rModule.compiler.(*libraryDecorator) 89 if !ok { 90 return "", false 91 } 92 if rustLib.source() { 93 switch rModule.hod { 94 case android.HostSupported, android.HostSupportedNoCross: 95 if rModule.Target().String() == ctx.Config().BuildOSTarget.String() { 96 src := rustLib.sourceProvider.Srcs()[0] 97 return src.String(), true 98 } 99 default: 100 if rModule.Target().String() == ctx.Config().AndroidFirstDeviceTarget.String() { 101 src := rustLib.sourceProvider.Srcs()[0] 102 return src.String(), true 103 } 104 } 105 } 106 return "", false 107} 108 109// sourceProviderSource finds the main source file of a source-provider crate. 110func sourceProviderSource(ctx android.SingletonContext, rModule *Module) (string, bool) { 111 rustLib, ok := rModule.compiler.(*libraryDecorator) 112 if !ok { 113 return "", false 114 } 115 if rustLib.source() { 116 // This is a source-variant, check if we are the right variant 117 // depending on the module configuration. 118 if src, ok := sourceProviderVariantSource(ctx, rModule); ok { 119 return src, true 120 } 121 } 122 foundSource := false 123 sourceSrc := "" 124 // Find the variant with the source and return its. 125 ctx.VisitAllModuleVariants(rModule, func(variant android.Module) { 126 if foundSource { 127 return 128 } 129 // All variants of a source provider library are libraries. 130 rVariant, _ := variant.(*Module) 131 variantLib, _ := rVariant.compiler.(*libraryDecorator) 132 if variantLib.source() { 133 sourceSrc, ok = sourceProviderVariantSource(ctx, rVariant) 134 if ok { 135 foundSource = true 136 } 137 } 138 }) 139 if !foundSource { 140 ctx.Errorf("No valid source for source provider found: %v\n", rModule) 141 } 142 return sourceSrc, foundSource 143} 144 145// crateSource finds the main source file (.rs) for a crate. 146func crateSource(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (string, bool) { 147 // Basic libraries, executables and tests. 148 srcs := comp.Properties.Srcs 149 if len(srcs) != 0 { 150 return path.Join(ctx.ModuleDir(rModule), srcs[0]), true 151 } 152 // SourceProvider libraries. 153 if rModule.sourceProvider != nil { 154 return sourceProviderSource(ctx, rModule) 155 } 156 return "", false 157} 158 159// mergeDependencies visits all the dependencies for module and updates crate and deps 160// with any new dependency. 161func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext, 162 module *Module, crate *rustProjectCrate, deps map[string]int) { 163 164 ctx.VisitDirectDeps(module, func(child android.Module) { 165 // Skip intra-module dependencies (i.e., generated-source library depending on the source variant). 166 if module.Name() == child.Name() { 167 return 168 } 169 // Skip unsupported modules. 170 rChild, compChild, ok := isModuleSupported(ctx, child) 171 if !ok { 172 return 173 } 174 // For unknown dependency, add it first. 175 var childId int 176 cInfo, known := singleton.knownCrates[rChild.Name()] 177 if !known { 178 childId, ok = singleton.addCrate(ctx, rChild, compChild) 179 if !ok { 180 return 181 } 182 } else { 183 childId = cInfo.Idx 184 } 185 // Is this dependency known already? 186 if _, ok = deps[child.Name()]; ok { 187 return 188 } 189 crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: rChild.CrateName()}) 190 deps[child.Name()] = childId 191 }) 192} 193 194// isModuleSupported returns the RustModule and baseCompiler if the module 195// should be considered for inclusion in rust-project.json. 196func isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, *baseCompiler, bool) { 197 rModule, ok := module.(*Module) 198 if !ok { 199 return nil, nil, false 200 } 201 if rModule.compiler == nil { 202 return nil, nil, false 203 } 204 var comp *baseCompiler 205 switch c := rModule.compiler.(type) { 206 case *libraryDecorator: 207 comp = c.baseCompiler 208 case *binaryDecorator: 209 comp = c.baseCompiler 210 case *testDecorator: 211 comp = c.binaryDecorator.baseCompiler 212 default: 213 return nil, nil, false 214 } 215 return rModule, comp, true 216} 217 218// addCrate adds a crate to singleton.project.Crates ensuring that required 219// dependencies are also added. It returns the index of the new crate in 220// singleton.project.Crates 221func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (int, bool) { 222 rootModule, ok := crateSource(ctx, rModule, comp) 223 if !ok { 224 ctx.Errorf("Unable to find source for valid module: %v", rModule) 225 return 0, false 226 } 227 228 crate := rustProjectCrate{ 229 DisplayName: rModule.Name(), 230 RootModule: rootModule, 231 Edition: comp.edition(), 232 Deps: make([]rustProjectDep, 0), 233 Cfg: make([]string, 0), 234 Env: make(map[string]string), 235 } 236 237 if comp.CargoOutDir().Valid() { 238 crate.Env["OUT_DIR"] = comp.CargoOutDir().String() 239 } 240 241 for _, feature := range comp.Properties.Features { 242 crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"") 243 } 244 245 deps := make(map[string]int) 246 singleton.mergeDependencies(ctx, rModule, &crate, deps) 247 248 idx := len(singleton.project.Crates) 249 singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps} 250 singleton.project.Crates = append(singleton.project.Crates, crate) 251 // rust-analyzer requires that all crates belong to at least one root: 252 // https://github.com/rust-analyzer/rust-analyzer/issues/4735. 253 singleton.project.Roots = append(singleton.project.Roots, path.Dir(crate.RootModule)) 254 return idx, true 255} 256 257// appendCrateAndDependencies creates a rustProjectCrate for the module argument and appends it to singleton.project. 258// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the 259// current module is already in singleton.knownCrates, its dependencies are merged. 260func (singleton *projectGeneratorSingleton) appendCrateAndDependencies(ctx android.SingletonContext, module android.Module) { 261 rModule, comp, ok := isModuleSupported(ctx, module) 262 if !ok { 263 return 264 } 265 // If we have seen this crate already; merge any new dependencies. 266 if cInfo, ok := singleton.knownCrates[module.Name()]; ok { 267 crate := singleton.project.Crates[cInfo.Idx] 268 singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps) 269 singleton.project.Crates[cInfo.Idx] = crate 270 return 271 } 272 singleton.addCrate(ctx, rModule, comp) 273} 274 275func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { 276 if !ctx.Config().IsEnvTrue(envVariableCollectRustDeps) { 277 return 278 } 279 280 singleton.knownCrates = make(map[string]crateInfo) 281 ctx.VisitAllModules(func(module android.Module) { 282 singleton.appendCrateAndDependencies(ctx, module) 283 }) 284 285 path := android.PathForOutput(ctx, rustProjectJsonFileName) 286 err := createJsonFile(singleton.project, path) 287 if err != nil { 288 ctx.Errorf(err.Error()) 289 } 290} 291 292func createJsonFile(project rustProjectJson, rustProjectPath android.WritablePath) error { 293 buf, err := json.MarshalIndent(project, "", " ") 294 if err != nil { 295 return fmt.Errorf("JSON marshal of rustProjectJson failed: %s", err) 296 } 297 err = android.WriteFileToOutputDir(rustProjectPath, buf, 0666) 298 if err != nil { 299 return fmt.Errorf("Writing rust-project to %s failed: %s", rustProjectPath.String(), err) 300 } 301 return nil 302} 303