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 21 "android/soong/android" 22) 23 24// This singleton collects Rust crate definitions and generates a JSON file 25// (${OUT_DIR}/soong/rust-project.json) which can be use by external tools, 26// such as rust-analyzer. It does so when either make, mm, mma, mmm or mmma is 27// called. This singleton is enabled only if SOONG_GEN_RUST_PROJECT is set. 28// For example, 29// 30// $ SOONG_GEN_RUST_PROJECT=1 m nothing 31 32const ( 33 // Environment variables used to control the behavior of this singleton. 34 envVariableCollectRustDeps = "SOONG_GEN_RUST_PROJECT" 35 rustProjectJsonFileName = "rust-project.json" 36) 37 38// The format of rust-project.json is not yet finalized. A current description is available at: 39// https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc#non-cargo-based-projects 40type rustProjectDep struct { 41 // The Crate attribute is the index of the dependency in the Crates array in rustProjectJson. 42 Crate int `json:"crate"` 43 Name string `json:"name"` 44} 45 46type rustProjectCrate struct { 47 DisplayName string `json:"display_name"` 48 RootModule string `json:"root_module"` 49 Edition string `json:"edition,omitempty"` 50 Deps []rustProjectDep `json:"deps"` 51 Cfg []string `json:"cfg"` 52 Env map[string]string `json:"env"` 53 ProcMacro bool `json:"is_proc_macro"` 54} 55 56type rustProjectJson struct { 57 Crates []rustProjectCrate `json:"crates"` 58} 59 60// crateInfo is used during the processing to keep track of the known crates. 61type crateInfo struct { 62 Idx int // Index of the crate in rustProjectJson.Crates slice. 63 Deps map[string]int // The keys are the module names and not the crate names. 64 Device bool // True if the crate at idx was a device crate 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.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton) 78} 79 80// mergeDependencies visits all the dependencies for module and updates crate and deps 81// with any new dependency. 82func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext, 83 module *Module, crate *rustProjectCrate, deps map[string]int) { 84 85 ctx.VisitDirectDeps(module, func(child android.Module) { 86 // Skip intra-module dependencies (i.e., generated-source library depending on the source variant). 87 if module.Name() == child.Name() { 88 return 89 } 90 // Skip unsupported modules. 91 rChild, ok := isModuleSupported(ctx, child) 92 if !ok { 93 return 94 } 95 // For unknown dependency, add it first. 96 var childId int 97 cInfo, known := singleton.knownCrates[rChild.Name()] 98 if !known { 99 childId, ok = singleton.addCrate(ctx, rChild) 100 if !ok { 101 return 102 } 103 } else { 104 childId = cInfo.Idx 105 } 106 // Is this dependency known already? 107 if _, ok = deps[child.Name()]; ok { 108 return 109 } 110 crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: rChild.CrateName()}) 111 deps[child.Name()] = childId 112 }) 113} 114 115// isModuleSupported returns the RustModule if the module 116// should be considered for inclusion in rust-project.json. 117func isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, bool) { 118 rModule, ok := module.(*Module) 119 if !ok { 120 return nil, false 121 } 122 if !rModule.Enabled(ctx) { 123 return nil, false 124 } 125 return rModule, true 126} 127 128// addCrate adds a crate to singleton.project.Crates ensuring that required 129// dependencies are also added. It returns the index of the new crate in 130// singleton.project.Crates 131func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module) (int, bool) { 132 deps := make(map[string]int) 133 rootModule, err := rModule.compiler.checkedCrateRootPath() 134 if err != nil { 135 return 0, false 136 } 137 138 _, procMacro := rModule.compiler.(*procMacroDecorator) 139 140 crate := rustProjectCrate{ 141 DisplayName: rModule.Name(), 142 RootModule: rootModule.String(), 143 Edition: rModule.compiler.edition(), 144 Deps: make([]rustProjectDep, 0), 145 Cfg: make([]string, 0), 146 Env: make(map[string]string), 147 ProcMacro: procMacro, 148 } 149 150 if rModule.compiler.cargoOutDir().Valid() { 151 crate.Env["OUT_DIR"] = rModule.compiler.cargoOutDir().String() 152 } 153 154 for _, feature := range rModule.compiler.features() { 155 crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"") 156 } 157 158 singleton.mergeDependencies(ctx, rModule, &crate, deps) 159 160 var idx int 161 if cInfo, ok := singleton.knownCrates[rModule.Name()]; ok { 162 idx = cInfo.Idx 163 singleton.project.Crates[idx] = crate 164 } else { 165 idx = len(singleton.project.Crates) 166 singleton.project.Crates = append(singleton.project.Crates, crate) 167 } 168 singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps, Device: rModule.Device()} 169 return idx, true 170} 171 172// appendCrateAndDependencies creates a rustProjectCrate for the module argument and appends it to singleton.project. 173// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the 174// current module is already in singleton.knownCrates, its dependencies are merged. 175func (singleton *projectGeneratorSingleton) appendCrateAndDependencies(ctx android.SingletonContext, module android.Module) { 176 rModule, ok := isModuleSupported(ctx, module) 177 if !ok { 178 return 179 } 180 // If we have seen this crate already; merge any new dependencies. 181 if cInfo, ok := singleton.knownCrates[module.Name()]; ok { 182 // If we have a new device variant, override the old one 183 if !cInfo.Device && rModule.Device() { 184 singleton.addCrate(ctx, rModule) 185 return 186 } 187 crate := singleton.project.Crates[cInfo.Idx] 188 singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps) 189 singleton.project.Crates[cInfo.Idx] = crate 190 return 191 } 192 singleton.addCrate(ctx, rModule) 193} 194 195func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { 196 if !ctx.Config().IsEnvTrue(envVariableCollectRustDeps) { 197 return 198 } 199 200 singleton.knownCrates = make(map[string]crateInfo) 201 ctx.VisitAllModules(func(module android.Module) { 202 singleton.appendCrateAndDependencies(ctx, module) 203 }) 204 205 path := android.PathForOutput(ctx, rustProjectJsonFileName) 206 err := createJsonFile(singleton.project, path) 207 if err != nil { 208 ctx.Errorf(err.Error()) 209 } 210} 211 212func createJsonFile(project rustProjectJson, rustProjectPath android.WritablePath) error { 213 buf, err := json.MarshalIndent(project, "", " ") 214 if err != nil { 215 return fmt.Errorf("JSON marshal of rustProjectJson failed: %s", err) 216 } 217 err = android.WriteFileToOutputDir(rustProjectPath, buf, 0666) 218 if err != nil { 219 return fmt.Errorf("Writing rust-project to %s failed: %s", rustProjectPath.String(), err) 220 } 221 return nil 222} 223