blob: c7cf5ed49a4b458b5ece3a7d35ca88fff4d1e080 [file] [log] [blame]
Kadir Çetinkaya07692002024-02-28 07:10:05 +00001/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Michael Merg6bafd752024-02-12 13:52:00 +000017// Binary ide_query generates and analyzes build artifacts.
18// The produced result can be consumed by IDEs to provide language features.
19package main
20
21import (
Kadir Çetinkaya07692002024-02-28 07:10:05 +000022 "bytes"
Michael Merg6bafd752024-02-12 13:52:00 +000023 "container/list"
24 "context"
25 "encoding/json"
26 "flag"
27 "fmt"
28 "log"
29 "os"
30 "os/exec"
31 "path"
32 "slices"
33 "strings"
34
35 "google.golang.org/protobuf/proto"
Michael Merg86cca742024-06-11 15:24:05 +000036 apb "ide_query/cc_analyzer_proto"
Michael Merg6bafd752024-02-12 13:52:00 +000037 pb "ide_query/ide_query_proto"
38)
39
40// Env contains information about the current environment.
41type Env struct {
Kadir Çetinkaya07692002024-02-28 07:10:05 +000042 LunchTarget LunchTarget
43 RepoDir string
44 OutDir string
45 ClangToolsRoot string
Michael Merg6bafd752024-02-12 13:52:00 +000046}
47
48// LunchTarget is a parsed Android lunch target.
49// Input format: <product_name>-<release_type>-<build_variant>
50type LunchTarget struct {
51 Product string
52 Release string
53 Variant string
54}
55
56var _ flag.Value = (*LunchTarget)(nil)
57
58// // Get implements flag.Value.
59// func (l *LunchTarget) Get() any {
60// return l
61// }
62
63// Set implements flag.Value.
64func (l *LunchTarget) Set(s string) error {
65 parts := strings.Split(s, "-")
66 if len(parts) != 3 {
67 return fmt.Errorf("invalid lunch target: %q, must have form <product_name>-<release_type>-<build_variant>", s)
68 }
69 *l = LunchTarget{
70 Product: parts[0],
71 Release: parts[1],
72 Variant: parts[2],
73 }
74 return nil
75}
76
77// String implements flag.Value.
78func (l *LunchTarget) String() string {
79 return fmt.Sprintf("%s-%s-%s", l.Product, l.Release, l.Variant)
80}
81
82func main() {
83 var env Env
Michael Merg86cca742024-06-11 15:24:05 +000084 env.OutDir = strings.TrimSuffix(os.Getenv("OUT_DIR"), "/")
Michael Merg6bafd752024-02-12 13:52:00 +000085 env.RepoDir = os.Getenv("ANDROID_BUILD_TOP")
Kadir Çetinkaya07692002024-02-28 07:10:05 +000086 env.ClangToolsRoot = os.Getenv("PREBUILTS_CLANG_TOOLS_ROOT")
Michael Merg6bafd752024-02-12 13:52:00 +000087 flag.Var(&env.LunchTarget, "lunch_target", "The lunch target to query")
88 flag.Parse()
89 files := flag.Args()
90 if len(files) == 0 {
91 fmt.Println("No files provided.")
92 os.Exit(1)
93 return
94 }
95
Michael Merg86cca742024-06-11 15:24:05 +000096 var ccFiles, javaFiles []string
Michael Merg6bafd752024-02-12 13:52:00 +000097 for _, f := range files {
98 switch {
99 case strings.HasSuffix(f, ".java") || strings.HasSuffix(f, ".kt"):
Michael Merg86cca742024-06-11 15:24:05 +0000100 javaFiles = append(javaFiles, f)
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000101 case strings.HasSuffix(f, ".cc") || strings.HasSuffix(f, ".cpp") || strings.HasSuffix(f, ".h"):
Michael Merg86cca742024-06-11 15:24:05 +0000102 ccFiles = append(ccFiles, f)
Michael Merg6bafd752024-02-12 13:52:00 +0000103 default:
104 log.Printf("File %q is supported - will be skipped.", f)
105 }
106 }
107
108 ctx := context.Background()
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000109 // TODO(michaelmerg): Figure out if module_bp_java_deps.json and compile_commands.json is outdated.
Michael Merg6bafd752024-02-12 13:52:00 +0000110 runMake(ctx, env, "nothing")
111
Michael Merg86cca742024-06-11 15:24:05 +0000112 javaModules, err := loadJavaModules(env)
Michael Merg6bafd752024-02-12 13:52:00 +0000113 if err != nil {
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000114 log.Printf("Failed to load java modules: %v", err)
115 }
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000116
Michael Merg86cca742024-06-11 15:24:05 +0000117 var targets []string
118 javaTargetsByFile := findJavaModules(javaFiles, javaModules)
119 for _, t := range javaTargetsByFile {
120 targets = append(targets, t)
Michael Merg6bafd752024-02-12 13:52:00 +0000121 }
122
Michael Merg86cca742024-06-11 15:24:05 +0000123 ccTargets, err := getCCTargets(ctx, env, ccFiles)
124 if err != nil {
125 log.Fatalf("Failed to query cc targets: %v", err)
126 }
127 targets = append(targets, ccTargets...)
128 if len(targets) == 0 {
129 fmt.Println("No targets found.")
130 os.Exit(1)
131 return
132 }
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000133
Michael Merg86cca742024-06-11 15:24:05 +0000134 fmt.Fprintf(os.Stderr, "Running make for modules: %v\n", strings.Join(targets, ", "))
135 if err := runMake(ctx, env, targets...); err != nil {
136 log.Printf("Building modules failed: %v", err)
137 }
138
139 var analysis pb.IdeAnalysis
140 results, units := getJavaInputs(env, javaTargetsByFile, javaModules)
141 analysis.Results = results
142 analysis.Units = units
143 if err != nil && analysis.Error == nil {
144 analysis.Error = &pb.AnalysisError{
145 ErrorMessage: err.Error(),
146 }
147 }
148
149 results, units, err = getCCInputs(ctx, env, ccFiles)
150 analysis.Results = append(analysis.Results, results...)
151 analysis.Units = append(analysis.Units, units...)
152 if err != nil && analysis.Error == nil {
153 analysis.Error = &pb.AnalysisError{
154 ErrorMessage: err.Error(),
155 }
156 }
157
158 analysis.BuildOutDir = env.OutDir
159 data, err := proto.Marshal(&analysis)
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000160 if err != nil {
161 log.Fatalf("Failed to marshal result proto: %v", err)
162 }
163
Michael Mergd8880ab2024-03-20 11:05:23 +0000164 _, err = os.Stdout.Write(data)
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000165 if err != nil {
166 log.Fatalf("Failed to write result proto: %v", err)
167 }
168
Michael Merg86cca742024-06-11 15:24:05 +0000169 for _, r := range analysis.Results {
170 fmt.Fprintf(os.Stderr, "%s: %+v\n", r.GetSourceFilePath(), r.GetStatus())
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000171 }
172}
173
Michael Merg86cca742024-06-11 15:24:05 +0000174func repoState(env Env, filePaths []string) *apb.RepoState {
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000175 const compDbPath = "soong/development/ide/compdb/compile_commands.json"
Michael Merg86cca742024-06-11 15:24:05 +0000176 return &apb.RepoState{
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000177 RepoDir: env.RepoDir,
Michael Merg86cca742024-06-11 15:24:05 +0000178 ActiveFilePath: filePaths,
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000179 OutDir: env.OutDir,
180 CompDbPath: path.Join(env.OutDir, compDbPath),
181 }
182}
183
Michael Merg86cca742024-06-11 15:24:05 +0000184func runCCanalyzer(ctx context.Context, env Env, mode string, in []byte) ([]byte, error) {
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000185 ccAnalyzerPath := path.Join(env.ClangToolsRoot, "bin/ide_query_cc_analyzer")
186 outBuffer := new(bytes.Buffer)
187
188 inBuffer := new(bytes.Buffer)
189 inBuffer.Write(in)
190
191 cmd := exec.CommandContext(ctx, ccAnalyzerPath, "--mode="+mode)
192 cmd.Dir = env.RepoDir
193
194 cmd.Stdin = inBuffer
195 cmd.Stdout = outBuffer
196 cmd.Stderr = os.Stderr
197
198 err := cmd.Run()
199
200 return outBuffer.Bytes(), err
201}
202
203// Execute cc_analyzer and get all the targets that needs to be build for analyzing files.
Michael Merg86cca742024-06-11 15:24:05 +0000204func getCCTargets(ctx context.Context, env Env, filePaths []string) ([]string, error) {
205 state, err := proto.Marshal(repoState(env, filePaths))
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000206 if err != nil {
207 log.Fatalln("Failed to serialize state:", err)
208 }
209
Michael Merg86cca742024-06-11 15:24:05 +0000210 resp := new(apb.DepsResponse)
211 result, err := runCCanalyzer(ctx, env, "deps", state)
212 if err != nil {
213 return nil, err
214 }
215
216 if err := proto.Unmarshal(result, resp); err != nil {
217 return nil, fmt.Errorf("malformed response from cc_analyzer: %v", err)
Michael Merg6bafd752024-02-12 13:52:00 +0000218 }
219
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000220 var targets []string
Michael Merg86cca742024-06-11 15:24:05 +0000221 if resp.Status != nil && resp.Status.Code != apb.Status_OK {
222 return targets, fmt.Errorf("cc_analyzer failed: %v", resp.Status.Message)
Michael Merg6bafd752024-02-12 13:52:00 +0000223 }
Michael Merg86cca742024-06-11 15:24:05 +0000224
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000225 for _, deps := range resp.Deps {
226 targets = append(targets, deps.BuildTarget...)
Michael Merg6bafd752024-02-12 13:52:00 +0000227 }
Michael Merg86cca742024-06-11 15:24:05 +0000228 return targets, nil
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000229}
230
Michael Merg86cca742024-06-11 15:24:05 +0000231func getCCInputs(ctx context.Context, env Env, filePaths []string) ([]*pb.AnalysisResult, []*pb.BuildableUnit, error) {
232 state, err := proto.Marshal(repoState(env, filePaths))
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000233 if err != nil {
234 log.Fatalln("Failed to serialize state:", err)
235 }
236
Michael Merg86cca742024-06-11 15:24:05 +0000237 resp := new(apb.IdeAnalysis)
238 result, err := runCCanalyzer(ctx, env, "inputs", state)
239 if err != nil {
240 return nil, nil, fmt.Errorf("cc_analyzer failed:", err)
241 }
242 if err := proto.Unmarshal(result, resp); err != nil {
243 return nil, nil, fmt.Errorf("malformed response from cc_analyzer: %v", err)
244 }
245 if resp.Status != nil && resp.Status.Code != apb.Status_OK {
246 return nil, nil, fmt.Errorf("cc_analyzer failed: %v", resp.Status.Message)
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000247 }
248
Michael Merg86cca742024-06-11 15:24:05 +0000249 var results []*pb.AnalysisResult
250 var units []*pb.BuildableUnit
251 for _, s := range resp.Sources {
252 status := &pb.AnalysisResult_Status{
253 Code: pb.AnalysisResult_Status_CODE_OK,
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000254 }
Michael Merg86cca742024-06-11 15:24:05 +0000255 if s.GetStatus().GetCode() != apb.Status_OK {
256 status.Code = pb.AnalysisResult_Status_CODE_BUILD_FAILED
257 status.StatusMessage = proto.String(s.GetStatus().GetMessage())
Michael Merg6bafd752024-02-12 13:52:00 +0000258 }
259
Michael Merg86cca742024-06-11 15:24:05 +0000260 result := &pb.AnalysisResult{
261 SourceFilePath: s.GetPath(),
262 UnitId: s.GetPath(),
263 Status: status,
Michael Merg6bafd752024-02-12 13:52:00 +0000264 }
Michael Merg86cca742024-06-11 15:24:05 +0000265 results = append(results, result)
Michael Merg6bafd752024-02-12 13:52:00 +0000266
Michael Merg6bafd752024-02-12 13:52:00 +0000267 var generated []*pb.GeneratedFile
Michael Merg86cca742024-06-11 15:24:05 +0000268 for _, f := range s.Generated {
269 generated = append(generated, &pb.GeneratedFile{
270 Path: f.GetPath(),
271 Contents: f.GetContents(),
272 })
273 }
274 genUnit := &pb.BuildableUnit{
275 Id: "genfiles_for_" + s.GetPath(),
276 SourceFilePaths: s.GetDeps(),
277 GeneratedFiles: generated,
278 }
Michael Merg6bafd752024-02-12 13:52:00 +0000279
Michael Merg86cca742024-06-11 15:24:05 +0000280 unit := &pb.BuildableUnit{
281 Id: s.GetPath(),
282 Language: pb.Language_LANGUAGE_CPP,
283 SourceFilePaths: []string{s.GetPath()},
284 CompilerArguments: s.GetCompilerArguments(),
285 DependencyIds: []string{genUnit.GetId()},
286 }
287 units = append(units, unit, genUnit)
288 }
289 return results, units, nil
290}
291
292// findJavaModules tries to find the modules that cover the given file paths.
293// If a file is covered by multiple modules, the first module is returned.
294func findJavaModules(paths []string, modules map[string]*javaModule) map[string]string {
295 ret := make(map[string]string)
Ilshat Aliyevcb4d82e2024-10-23 09:21:30 +0000296 // A file may be part of multiple modules. To make the result deterministic,
297 // check the modules in sorted order.
298 keys := make([]string, 0, len(modules))
299 for name := range modules {
300 keys = append(keys, name)
301 }
302 slices.Sort(keys)
303 for _, name := range keys {
Michael Merg86cca742024-06-11 15:24:05 +0000304 if strings.HasSuffix(name, ".impl") {
305 continue
306 }
307
Ilshat Aliyevcb4d82e2024-10-23 09:21:30 +0000308 module := modules[name]
Michael Merg86cca742024-06-11 15:24:05 +0000309 for i, p := range paths {
310 if slices.Contains(module.Srcs, p) {
311 ret[p] = name
312 paths = append(paths[:i], paths[i+1:]...)
313 break
Michael Merg6bafd752024-02-12 13:52:00 +0000314 }
315 }
Michael Merg86cca742024-06-11 15:24:05 +0000316 if len(paths) == 0 {
317 break
318 }
Michael Merg6bafd752024-02-12 13:52:00 +0000319 }
Michael Merg86cca742024-06-11 15:24:05 +0000320 return ret
321}
322
323func getJavaInputs(env Env, modulesByPath map[string]string, modules map[string]*javaModule) ([]*pb.AnalysisResult, []*pb.BuildableUnit) {
324 var results []*pb.AnalysisResult
325 unitsById := make(map[string]*pb.BuildableUnit)
326 for p, moduleName := range modulesByPath {
327 r := &pb.AnalysisResult{
328 SourceFilePath: p,
329 }
330 results = append(results, r)
331
332 m := modules[moduleName]
333 if m == nil {
334 r.Status = &pb.AnalysisResult_Status{
335 Code: pb.AnalysisResult_Status_CODE_NOT_FOUND,
336 StatusMessage: proto.String("File not found in any module."),
337 }
338 continue
339 }
340
341 r.UnitId = moduleName
342 r.Status = &pb.AnalysisResult_Status{Code: pb.AnalysisResult_Status_CODE_OK}
343 if unitsById[r.UnitId] != nil {
344 // File is covered by an already created unit.
345 continue
346 }
347
348 u := &pb.BuildableUnit{
349 Id: moduleName,
350 Language: pb.Language_LANGUAGE_JAVA,
351 SourceFilePaths: m.Srcs,
Ilshat Aliyev4d491112024-10-10 09:12:21 +0000352 GeneratedFiles: genFiles(env, m),
353 DependencyIds: m.Deps,
Michael Merg86cca742024-06-11 15:24:05 +0000354 }
355 unitsById[u.Id] = u
356
357 q := list.New()
358 for _, d := range m.Deps {
359 q.PushBack(d)
360 }
361 for q.Len() > 0 {
362 name := q.Remove(q.Front()).(string)
363 mod := modules[name]
364 if mod == nil || unitsById[name] != nil {
365 continue
366 }
367
Michael Merg86cca742024-06-11 15:24:05 +0000368 unitsById[name] = &pb.BuildableUnit{
369 Id: name,
370 SourceFilePaths: mod.Srcs,
Ilshat Aliyev4d491112024-10-10 09:12:21 +0000371 GeneratedFiles: genFiles(env, mod),
Ilshat Aliyevd297a092024-08-23 12:31:09 +0000372 DependencyIds: mod.Deps,
Michael Merg86cca742024-06-11 15:24:05 +0000373 }
374
375 for _, d := range mod.Deps {
376 q.PushBack(d)
377 }
378 }
Michael Merg6bafd752024-02-12 13:52:00 +0000379 }
Michael Merg86cca742024-06-11 15:24:05 +0000380
381 units := make([]*pb.BuildableUnit, 0, len(unitsById))
382 for _, u := range unitsById {
383 units = append(units, u)
384 }
385 return results, units
386}
387
388// genFiles returns the generated files (paths that start with outDir/) for the
Ilshat Aliyev4d491112024-10-10 09:12:21 +0000389// given module. Generated files that do not exist are ignored.
390func genFiles(env Env, mod *javaModule) []*pb.GeneratedFile {
391 var paths []string
392 paths = append(paths, mod.Srcs...)
393 paths = append(paths, mod.SrcJars...)
394 paths = append(paths, mod.Jars...)
395
Michael Merg86cca742024-06-11 15:24:05 +0000396 prefix := env.OutDir + "/"
397 var ret []*pb.GeneratedFile
398 for _, p := range paths {
399 relPath, ok := strings.CutPrefix(p, prefix)
400 if !ok {
401 continue
402 }
403
404 contents, err := os.ReadFile(path.Join(env.RepoDir, p))
405 if err != nil {
406 continue
407 }
408
409 ret = append(ret, &pb.GeneratedFile{
410 Path: relPath,
411 Contents: contents,
412 })
413 }
414 return ret
Michael Merg6bafd752024-02-12 13:52:00 +0000415}
416
417// runMake runs Soong build for the given modules.
418func runMake(ctx context.Context, env Env, modules ...string) error {
419 args := []string{
420 "--make-mode",
421 "ANDROID_BUILD_ENVIRONMENT_CONFIG=googler-cog",
Michael Merg75d934f2024-04-18 12:06:31 +0000422 "SOONG_GEN_COMPDB=1",
Michael Merg6bafd752024-02-12 13:52:00 +0000423 "TARGET_PRODUCT=" + env.LunchTarget.Product,
424 "TARGET_RELEASE=" + env.LunchTarget.Release,
425 "TARGET_BUILD_VARIANT=" + env.LunchTarget.Variant,
Michael Merg86cca742024-06-11 15:24:05 +0000426 "TARGET_BUILD_TYPE=release",
Kadir Çetinkaya874e12b2024-03-15 07:27:30 +0000427 "-k",
Michael Merg6bafd752024-02-12 13:52:00 +0000428 }
429 args = append(args, modules...)
430 cmd := exec.CommandContext(ctx, "build/soong/soong_ui.bash", args...)
431 cmd.Dir = env.RepoDir
Michael Mergd8880ab2024-03-20 11:05:23 +0000432 cmd.Stdout = os.Stderr
Michael Merg6bafd752024-02-12 13:52:00 +0000433 cmd.Stderr = os.Stderr
434 return cmd.Run()
435}
436
437type javaModule struct {
Michael Merg6bafd752024-02-12 13:52:00 +0000438 Path []string `json:"path,omitempty"`
439 Deps []string `json:"dependencies,omitempty"`
440 Srcs []string `json:"srcs,omitempty"`
441 Jars []string `json:"jars,omitempty"`
442 SrcJars []string `json:"srcjars,omitempty"`
443}
444
Michael Merg86cca742024-06-11 15:24:05 +0000445func loadJavaModules(env Env) (map[string]*javaModule, error) {
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000446 javaDepsPath := path.Join(env.RepoDir, env.OutDir, "soong/module_bp_java_deps.json")
447 data, err := os.ReadFile(javaDepsPath)
Michael Merg6bafd752024-02-12 13:52:00 +0000448 if err != nil {
Michael Merg86cca742024-06-11 15:24:05 +0000449 return nil, err
Michael Merg6bafd752024-02-12 13:52:00 +0000450 }
451
Michael Merg86cca742024-06-11 15:24:05 +0000452 var ret map[string]*javaModule // module name -> module
453 if err = json.Unmarshal(data, &ret); err != nil {
454 return nil, err
Michael Merg6bafd752024-02-12 13:52:00 +0000455 }
456
Michael Merg86cca742024-06-11 15:24:05 +0000457 // Add top level java_sdk_library for .impl modules.
458 for name, module := range ret {
459 if striped := strings.TrimSuffix(name, ".impl"); striped != name {
460 ret[striped] = module
Kadir Çetinkaya07692002024-02-28 07:10:05 +0000461 }
Michael Merg6bafd752024-02-12 13:52:00 +0000462 }
Michael Merg86cca742024-06-11 15:24:05 +0000463 return ret, nil
Michael Merg6bafd752024-02-12 13:52:00 +0000464}