xref: /aosp_15_r20/build/soong/rust/project_json.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1*333d2b36SAndroid Build Coastguard Worker// Copyright 2020 Google Inc. All rights reserved.
2*333d2b36SAndroid Build Coastguard Worker//
3*333d2b36SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*333d2b36SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*333d2b36SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*333d2b36SAndroid Build Coastguard Worker//
7*333d2b36SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*333d2b36SAndroid Build Coastguard Worker//
9*333d2b36SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*333d2b36SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*333d2b36SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*333d2b36SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*333d2b36SAndroid Build Coastguard Worker// limitations under the License.
14*333d2b36SAndroid Build Coastguard Worker
15*333d2b36SAndroid Build Coastguard Workerpackage rust
16*333d2b36SAndroid Build Coastguard Worker
17*333d2b36SAndroid Build Coastguard Workerimport (
18*333d2b36SAndroid Build Coastguard Worker	"encoding/json"
19*333d2b36SAndroid Build Coastguard Worker	"fmt"
20*333d2b36SAndroid Build Coastguard Worker
21*333d2b36SAndroid Build Coastguard Worker	"android/soong/android"
22*333d2b36SAndroid Build Coastguard Worker	"android/soong/rust/config"
23*333d2b36SAndroid Build Coastguard Worker)
24*333d2b36SAndroid Build Coastguard Worker
25*333d2b36SAndroid Build Coastguard Worker// This singleton collects Rust crate definitions and generates a JSON file
26*333d2b36SAndroid Build Coastguard Worker// (${OUT_DIR}/soong/rust-project.json) which can be use by external tools,
27*333d2b36SAndroid Build Coastguard Worker// such as rust-analyzer. It does so when either make, mm, mma, mmm or mmma is
28*333d2b36SAndroid Build Coastguard Worker// called.  This singleton is enabled only if SOONG_GEN_RUST_PROJECT is set.
29*333d2b36SAndroid Build Coastguard Worker// For example,
30*333d2b36SAndroid Build Coastguard Worker//
31*333d2b36SAndroid Build Coastguard Worker//   $ SOONG_GEN_RUST_PROJECT=1 m nothing
32*333d2b36SAndroid Build Coastguard Worker
33*333d2b36SAndroid Build Coastguard Workerconst (
34*333d2b36SAndroid Build Coastguard Worker	// Environment variables used to control the behavior of this singleton.
35*333d2b36SAndroid Build Coastguard Worker	envVariableCollectRustDeps = "SOONG_GEN_RUST_PROJECT"
36*333d2b36SAndroid Build Coastguard Worker	rustProjectJsonFileName    = "rust-project.json"
37*333d2b36SAndroid Build Coastguard Worker)
38*333d2b36SAndroid Build Coastguard Worker
39*333d2b36SAndroid Build Coastguard Worker// The format of rust-project.json is not yet finalized. A current description is available at:
40*333d2b36SAndroid Build Coastguard Worker// https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc#non-cargo-based-projects
41*333d2b36SAndroid Build Coastguard Workertype rustProjectDep struct {
42*333d2b36SAndroid Build Coastguard Worker	// The Crate attribute is the index of the dependency in the Crates array in rustProjectJson.
43*333d2b36SAndroid Build Coastguard Worker	Crate int    `json:"crate"`
44*333d2b36SAndroid Build Coastguard Worker	Name  string `json:"name"`
45*333d2b36SAndroid Build Coastguard Worker}
46*333d2b36SAndroid Build Coastguard Worker
47*333d2b36SAndroid Build Coastguard Workertype rustProjectCrate struct {
48*333d2b36SAndroid Build Coastguard Worker	DisplayName    string            `json:"display_name"`
49*333d2b36SAndroid Build Coastguard Worker	RootModule     string            `json:"root_module"`
50*333d2b36SAndroid Build Coastguard Worker	Edition        string            `json:"edition,omitempty"`
51*333d2b36SAndroid Build Coastguard Worker	Deps           []rustProjectDep  `json:"deps"`
52*333d2b36SAndroid Build Coastguard Worker	Cfg            []string          `json:"cfg"`
53*333d2b36SAndroid Build Coastguard Worker	Env            map[string]string `json:"env"`
54*333d2b36SAndroid Build Coastguard Worker	ProcMacro      bool              `json:"is_proc_macro"`
55*333d2b36SAndroid Build Coastguard Worker	ProcMacroDylib *string           `json:"proc_macro_dylib_path"`
56*333d2b36SAndroid Build Coastguard Worker}
57*333d2b36SAndroid Build Coastguard Worker
58*333d2b36SAndroid Build Coastguard Workertype rustProjectJson struct {
59*333d2b36SAndroid Build Coastguard Worker	Sysroot string             `json:"sysroot"`
60*333d2b36SAndroid Build Coastguard Worker	Crates  []rustProjectCrate `json:"crates"`
61*333d2b36SAndroid Build Coastguard Worker}
62*333d2b36SAndroid Build Coastguard Worker
63*333d2b36SAndroid Build Coastguard Worker// crateInfo is used during the processing to keep track of the known crates.
64*333d2b36SAndroid Build Coastguard Workertype crateInfo struct {
65*333d2b36SAndroid Build Coastguard Worker	Idx    int            // Index of the crate in rustProjectJson.Crates slice.
66*333d2b36SAndroid Build Coastguard Worker	Deps   map[string]int // The keys are the module names and not the crate names.
67*333d2b36SAndroid Build Coastguard Worker	Device bool           // True if the crate at idx was a device crate
68*333d2b36SAndroid Build Coastguard Worker}
69*333d2b36SAndroid Build Coastguard Worker
70*333d2b36SAndroid Build Coastguard Workertype projectGeneratorSingleton struct {
71*333d2b36SAndroid Build Coastguard Worker	project     rustProjectJson
72*333d2b36SAndroid Build Coastguard Worker	knownCrates map[string]crateInfo // Keys are module names.
73*333d2b36SAndroid Build Coastguard Worker}
74*333d2b36SAndroid Build Coastguard Worker
75*333d2b36SAndroid Build Coastguard Workerfunc rustProjectGeneratorSingleton() android.Singleton {
76*333d2b36SAndroid Build Coastguard Worker	return &projectGeneratorSingleton{}
77*333d2b36SAndroid Build Coastguard Worker}
78*333d2b36SAndroid Build Coastguard Worker
79*333d2b36SAndroid Build Coastguard Workerfunc init() {
80*333d2b36SAndroid Build Coastguard Worker	android.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
81*333d2b36SAndroid Build Coastguard Worker}
82*333d2b36SAndroid Build Coastguard Worker
83*333d2b36SAndroid Build Coastguard Worker// mergeDependencies visits all the dependencies for module and updates crate and deps
84*333d2b36SAndroid Build Coastguard Worker// with any new dependency.
85*333d2b36SAndroid Build Coastguard Workerfunc (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext,
86*333d2b36SAndroid Build Coastguard Worker	module *Module, crate *rustProjectCrate, deps map[string]int) {
87*333d2b36SAndroid Build Coastguard Worker
88*333d2b36SAndroid Build Coastguard Worker	ctx.VisitDirectDeps(module, func(child android.Module) {
89*333d2b36SAndroid Build Coastguard Worker		// Skip intra-module dependencies (i.e., generated-source library depending on the source variant).
90*333d2b36SAndroid Build Coastguard Worker		if module.Name() == child.Name() {
91*333d2b36SAndroid Build Coastguard Worker			return
92*333d2b36SAndroid Build Coastguard Worker		}
93*333d2b36SAndroid Build Coastguard Worker		// Skip unsupported modules.
94*333d2b36SAndroid Build Coastguard Worker		rChild, ok := isModuleSupported(ctx, child)
95*333d2b36SAndroid Build Coastguard Worker		if !ok {
96*333d2b36SAndroid Build Coastguard Worker			return
97*333d2b36SAndroid Build Coastguard Worker		}
98*333d2b36SAndroid Build Coastguard Worker		// For unknown dependency, add it first.
99*333d2b36SAndroid Build Coastguard Worker		var childId int
100*333d2b36SAndroid Build Coastguard Worker		cInfo, known := singleton.knownCrates[rChild.Name()]
101*333d2b36SAndroid Build Coastguard Worker		if !known {
102*333d2b36SAndroid Build Coastguard Worker			childId, ok = singleton.addCrate(ctx, rChild)
103*333d2b36SAndroid Build Coastguard Worker			if !ok {
104*333d2b36SAndroid Build Coastguard Worker				return
105*333d2b36SAndroid Build Coastguard Worker			}
106*333d2b36SAndroid Build Coastguard Worker		} else {
107*333d2b36SAndroid Build Coastguard Worker			childId = cInfo.Idx
108*333d2b36SAndroid Build Coastguard Worker		}
109*333d2b36SAndroid Build Coastguard Worker		// Is this dependency known already?
110*333d2b36SAndroid Build Coastguard Worker		if _, ok = deps[child.Name()]; ok {
111*333d2b36SAndroid Build Coastguard Worker			return
112*333d2b36SAndroid Build Coastguard Worker		}
113*333d2b36SAndroid Build Coastguard Worker		crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: rChild.CrateName()})
114*333d2b36SAndroid Build Coastguard Worker		deps[child.Name()] = childId
115*333d2b36SAndroid Build Coastguard Worker	})
116*333d2b36SAndroid Build Coastguard Worker}
117*333d2b36SAndroid Build Coastguard Worker
118*333d2b36SAndroid Build Coastguard Worker// isModuleSupported returns the RustModule if the module
119*333d2b36SAndroid Build Coastguard Worker// should be considered for inclusion in rust-project.json.
120*333d2b36SAndroid Build Coastguard Workerfunc isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, bool) {
121*333d2b36SAndroid Build Coastguard Worker	rModule, ok := module.(*Module)
122*333d2b36SAndroid Build Coastguard Worker	if !ok {
123*333d2b36SAndroid Build Coastguard Worker		return nil, false
124*333d2b36SAndroid Build Coastguard Worker	}
125*333d2b36SAndroid Build Coastguard Worker	if !rModule.Enabled(ctx) {
126*333d2b36SAndroid Build Coastguard Worker		return nil, false
127*333d2b36SAndroid Build Coastguard Worker	}
128*333d2b36SAndroid Build Coastguard Worker	return rModule, true
129*333d2b36SAndroid Build Coastguard Worker}
130*333d2b36SAndroid Build Coastguard Worker
131*333d2b36SAndroid Build Coastguard Worker// addCrate adds a crate to singleton.project.Crates ensuring that required
132*333d2b36SAndroid Build Coastguard Worker// dependencies are also added. It returns the index of the new crate in
133*333d2b36SAndroid Build Coastguard Worker// singleton.project.Crates
134*333d2b36SAndroid Build Coastguard Workerfunc (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module) (int, bool) {
135*333d2b36SAndroid Build Coastguard Worker	deps := make(map[string]int)
136*333d2b36SAndroid Build Coastguard Worker	rootModule, err := rModule.compiler.checkedCrateRootPath()
137*333d2b36SAndroid Build Coastguard Worker	if err != nil {
138*333d2b36SAndroid Build Coastguard Worker		return 0, false
139*333d2b36SAndroid Build Coastguard Worker	}
140*333d2b36SAndroid Build Coastguard Worker
141*333d2b36SAndroid Build Coastguard Worker	var procMacroDylib *string = nil
142*333d2b36SAndroid Build Coastguard Worker	if procDec, procMacro := rModule.compiler.(*procMacroDecorator); procMacro {
143*333d2b36SAndroid Build Coastguard Worker		procMacroDylib = new(string)
144*333d2b36SAndroid Build Coastguard Worker		*procMacroDylib = procDec.baseCompiler.unstrippedOutputFilePath().String()
145*333d2b36SAndroid Build Coastguard Worker	}
146*333d2b36SAndroid Build Coastguard Worker
147*333d2b36SAndroid Build Coastguard Worker	crate := rustProjectCrate{
148*333d2b36SAndroid Build Coastguard Worker		DisplayName:    rModule.Name(),
149*333d2b36SAndroid Build Coastguard Worker		RootModule:     rootModule.String(),
150*333d2b36SAndroid Build Coastguard Worker		Edition:        rModule.compiler.edition(),
151*333d2b36SAndroid Build Coastguard Worker		Deps:           make([]rustProjectDep, 0),
152*333d2b36SAndroid Build Coastguard Worker		Cfg:            make([]string, 0),
153*333d2b36SAndroid Build Coastguard Worker		Env:            make(map[string]string),
154*333d2b36SAndroid Build Coastguard Worker		ProcMacro:      procMacroDylib != nil,
155*333d2b36SAndroid Build Coastguard Worker		ProcMacroDylib: procMacroDylib,
156*333d2b36SAndroid Build Coastguard Worker	}
157*333d2b36SAndroid Build Coastguard Worker
158*333d2b36SAndroid Build Coastguard Worker	if rModule.compiler.cargoOutDir().Valid() {
159*333d2b36SAndroid Build Coastguard Worker		crate.Env["OUT_DIR"] = rModule.compiler.cargoOutDir().String()
160*333d2b36SAndroid Build Coastguard Worker	}
161*333d2b36SAndroid Build Coastguard Worker
162*333d2b36SAndroid Build Coastguard Worker	for _, feature := range rModule.compiler.features(ctx, rModule) {
163*333d2b36SAndroid Build Coastguard Worker		crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"")
164*333d2b36SAndroid Build Coastguard Worker	}
165*333d2b36SAndroid Build Coastguard Worker
166*333d2b36SAndroid Build Coastguard Worker	singleton.mergeDependencies(ctx, rModule, &crate, deps)
167*333d2b36SAndroid Build Coastguard Worker
168*333d2b36SAndroid Build Coastguard Worker	var idx int
169*333d2b36SAndroid Build Coastguard Worker	if cInfo, ok := singleton.knownCrates[rModule.Name()]; ok {
170*333d2b36SAndroid Build Coastguard Worker		idx = cInfo.Idx
171*333d2b36SAndroid Build Coastguard Worker		singleton.project.Crates[idx] = crate
172*333d2b36SAndroid Build Coastguard Worker	} else {
173*333d2b36SAndroid Build Coastguard Worker		idx = len(singleton.project.Crates)
174*333d2b36SAndroid Build Coastguard Worker		singleton.project.Crates = append(singleton.project.Crates, crate)
175*333d2b36SAndroid Build Coastguard Worker	}
176*333d2b36SAndroid Build Coastguard Worker	singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps, Device: rModule.Device()}
177*333d2b36SAndroid Build Coastguard Worker	return idx, true
178*333d2b36SAndroid Build Coastguard Worker}
179*333d2b36SAndroid Build Coastguard Worker
180*333d2b36SAndroid Build Coastguard Worker// appendCrateAndDependencies creates a rustProjectCrate for the module argument and appends it to singleton.project.
181*333d2b36SAndroid Build Coastguard Worker// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the
182*333d2b36SAndroid Build Coastguard Worker// current module is already in singleton.knownCrates, its dependencies are merged.
183*333d2b36SAndroid Build Coastguard Workerfunc (singleton *projectGeneratorSingleton) appendCrateAndDependencies(ctx android.SingletonContext, module android.Module) {
184*333d2b36SAndroid Build Coastguard Worker	rModule, ok := isModuleSupported(ctx, module)
185*333d2b36SAndroid Build Coastguard Worker	if !ok {
186*333d2b36SAndroid Build Coastguard Worker		return
187*333d2b36SAndroid Build Coastguard Worker	}
188*333d2b36SAndroid Build Coastguard Worker	// If we have seen this crate already; merge any new dependencies.
189*333d2b36SAndroid Build Coastguard Worker	if cInfo, ok := singleton.knownCrates[module.Name()]; ok {
190*333d2b36SAndroid Build Coastguard Worker		// If we have a new device variant, override the old one
191*333d2b36SAndroid Build Coastguard Worker		if !cInfo.Device && rModule.Device() {
192*333d2b36SAndroid Build Coastguard Worker			singleton.addCrate(ctx, rModule)
193*333d2b36SAndroid Build Coastguard Worker			return
194*333d2b36SAndroid Build Coastguard Worker		}
195*333d2b36SAndroid Build Coastguard Worker		crate := singleton.project.Crates[cInfo.Idx]
196*333d2b36SAndroid Build Coastguard Worker		singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps)
197*333d2b36SAndroid Build Coastguard Worker		singleton.project.Crates[cInfo.Idx] = crate
198*333d2b36SAndroid Build Coastguard Worker		return
199*333d2b36SAndroid Build Coastguard Worker	}
200*333d2b36SAndroid Build Coastguard Worker	singleton.addCrate(ctx, rModule)
201*333d2b36SAndroid Build Coastguard Worker}
202*333d2b36SAndroid Build Coastguard Worker
203*333d2b36SAndroid Build Coastguard Workerfunc (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
204*333d2b36SAndroid Build Coastguard Worker	if !ctx.Config().IsEnvTrue(envVariableCollectRustDeps) {
205*333d2b36SAndroid Build Coastguard Worker		return
206*333d2b36SAndroid Build Coastguard Worker	}
207*333d2b36SAndroid Build Coastguard Worker
208*333d2b36SAndroid Build Coastguard Worker	singleton.project.Sysroot = config.RustPath(ctx)
209*333d2b36SAndroid Build Coastguard Worker
210*333d2b36SAndroid Build Coastguard Worker	singleton.knownCrates = make(map[string]crateInfo)
211*333d2b36SAndroid Build Coastguard Worker	ctx.VisitAllModules(func(module android.Module) {
212*333d2b36SAndroid Build Coastguard Worker		singleton.appendCrateAndDependencies(ctx, module)
213*333d2b36SAndroid Build Coastguard Worker	})
214*333d2b36SAndroid Build Coastguard Worker
215*333d2b36SAndroid Build Coastguard Worker	path := android.PathForOutput(ctx, rustProjectJsonFileName)
216*333d2b36SAndroid Build Coastguard Worker	err := createJsonFile(singleton.project, path)
217*333d2b36SAndroid Build Coastguard Worker	if err != nil {
218*333d2b36SAndroid Build Coastguard Worker		ctx.Errorf(err.Error())
219*333d2b36SAndroid Build Coastguard Worker	}
220*333d2b36SAndroid Build Coastguard Worker}
221*333d2b36SAndroid Build Coastguard Worker
222*333d2b36SAndroid Build Coastguard Workerfunc createJsonFile(project rustProjectJson, rustProjectPath android.WritablePath) error {
223*333d2b36SAndroid Build Coastguard Worker	buf, err := json.MarshalIndent(project, "", "  ")
224*333d2b36SAndroid Build Coastguard Worker	if err != nil {
225*333d2b36SAndroid Build Coastguard Worker		return fmt.Errorf("JSON marshal of rustProjectJson failed: %s", err)
226*333d2b36SAndroid Build Coastguard Worker	}
227*333d2b36SAndroid Build Coastguard Worker	err = android.WriteFileToOutputDir(rustProjectPath, buf, 0666)
228*333d2b36SAndroid Build Coastguard Worker	if err != nil {
229*333d2b36SAndroid Build Coastguard Worker		return fmt.Errorf("Writing rust-project to %s failed: %s", rustProjectPath.String(), err)
230*333d2b36SAndroid Build Coastguard Worker	}
231*333d2b36SAndroid Build Coastguard Worker	return nil
232*333d2b36SAndroid Build Coastguard Worker}
233