platform_build_soong/rust/project_json.go
Cole Faust 021bf3d973 Reapply "Make the enabled property configurable"
Previously, I had changed some loadhook-appended property structs
to use selects instead of the "target" property struct. This seems
to not be exactly equivalent because "target" properties are merged
with the regular properties later, at the time the arch mutator runs.

With this reapplication, leave those target property structs alone
to avoid breakages, but I'll have to look into what the issue is
with them later.

This reverts commit ed5276f082.

Ignore-AOSP-First: This cl needs to be in a topic with internal-only projects, will cherrypick to aosp after.
Bug: 323382414
Test: m nothing --no-skip-soong-tests
Change-Id: If355d24506e3f117d27b21442a6c02bca3402dc7
2024-05-02 10:56:36 -07:00

222 lines
7.3 KiB
Go

// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rust
import (
"encoding/json"
"fmt"
"android/soong/android"
)
// This singleton collects Rust crate definitions and generates a JSON file
// (${OUT_DIR}/soong/rust-project.json) which can be use by external tools,
// such as rust-analyzer. It does so when either make, mm, mma, mmm or mmma is
// called. This singleton is enabled only if SOONG_GEN_RUST_PROJECT is set.
// For example,
//
// $ SOONG_GEN_RUST_PROJECT=1 m nothing
const (
// Environment variables used to control the behavior of this singleton.
envVariableCollectRustDeps = "SOONG_GEN_RUST_PROJECT"
rustProjectJsonFileName = "rust-project.json"
)
// The format of rust-project.json is not yet finalized. A current description is available at:
// https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc#non-cargo-based-projects
type rustProjectDep struct {
// The Crate attribute is the index of the dependency in the Crates array in rustProjectJson.
Crate int `json:"crate"`
Name string `json:"name"`
}
type rustProjectCrate struct {
DisplayName string `json:"display_name"`
RootModule string `json:"root_module"`
Edition string `json:"edition,omitempty"`
Deps []rustProjectDep `json:"deps"`
Cfg []string `json:"cfg"`
Env map[string]string `json:"env"`
ProcMacro bool `json:"is_proc_macro"`
}
type rustProjectJson struct {
Crates []rustProjectCrate `json:"crates"`
}
// crateInfo is used during the processing to keep track of the known crates.
type crateInfo struct {
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.
Device bool // True if the crate at idx was a device crate
}
type projectGeneratorSingleton struct {
project rustProjectJson
knownCrates map[string]crateInfo // Keys are module names.
}
func rustProjectGeneratorSingleton() android.Singleton {
return &projectGeneratorSingleton{}
}
func init() {
android.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
}
// 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
}
// Skip unsupported modules.
rChild, ok := isModuleSupported(ctx, child)
if !ok {
return
}
// For unknown dependency, add it first.
var childId int
cInfo, known := singleton.knownCrates[rChild.Name()]
if !known {
childId, ok = singleton.addCrate(ctx, rChild)
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
})
}
// isModuleSupported returns the RustModule if the module
// should be considered for inclusion in rust-project.json.
func isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, bool) {
rModule, ok := module.(*Module)
if !ok {
return nil, false
}
if !rModule.Enabled(ctx) {
return nil, false
}
return rModule, 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) (int, bool) {
deps := make(map[string]int)
rootModule, err := rModule.compiler.checkedCrateRootPath()
if err != nil {
return 0, false
}
_, procMacro := rModule.compiler.(*procMacroDecorator)
crate := rustProjectCrate{
DisplayName: rModule.Name(),
RootModule: rootModule.String(),
Edition: rModule.compiler.edition(),
Deps: make([]rustProjectDep, 0),
Cfg: make([]string, 0),
Env: make(map[string]string),
ProcMacro: procMacro,
}
if rModule.compiler.cargoOutDir().Valid() {
crate.Env["OUT_DIR"] = rModule.compiler.cargoOutDir().String()
}
for _, feature := range rModule.compiler.features() {
crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"")
}
singleton.mergeDependencies(ctx, rModule, &crate, deps)
var idx int
if cInfo, ok := singleton.knownCrates[rModule.Name()]; ok {
idx = cInfo.Idx
singleton.project.Crates[idx] = crate
} else {
idx = len(singleton.project.Crates)
singleton.project.Crates = append(singleton.project.Crates, crate)
}
singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps, Device: rModule.Device()}
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, 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 {
// If we have a new device variant, override the old one
if !cInfo.Device && rModule.Device() {
singleton.addCrate(ctx, rModule)
return
}
crate := singleton.project.Crates[cInfo.Idx]
singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps)
singleton.project.Crates[cInfo.Idx] = crate
return
}
singleton.addCrate(ctx, rModule)
}
func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
if !ctx.Config().IsEnvTrue(envVariableCollectRustDeps) {
return
}
singleton.knownCrates = make(map[string]crateInfo)
ctx.VisitAllModules(func(module android.Module) {
singleton.appendCrateAndDependencies(ctx, module)
})
path := android.PathForOutput(ctx, rustProjectJsonFileName)
err := createJsonFile(singleton.project, path)
if err != nil {
ctx.Errorf(err.Error())
}
}
func createJsonFile(project rustProjectJson, rustProjectPath android.WritablePath) error {
buf, err := json.MarshalIndent(project, "", " ")
if err != nil {
return fmt.Errorf("JSON marshal of rustProjectJson failed: %s", err)
}
err = android.WriteFileToOutputDir(rustProjectPath, buf, 0666)
if err != nil {
return fmt.Errorf("Writing rust-project to %s failed: %s", rustProjectPath.String(), err)
}
return nil
}