// Copyright 2019 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 cc import ( "encoding/json" "fmt" "path" "sort" "strings" "android/soong/android" ) // This singleton collects cc modules' source and flags into to a json file. // It does so for generating CMakeLists.txt project files needed data when // either make, mm, mma, mmm or mmma is called. // The info file is generated in $OUT/module_bp_cc_depend.json. func init() { android.RegisterSingletonType("ccdeps_generator", ccDepsGeneratorSingleton) } func ccDepsGeneratorSingleton() android.Singleton { return &ccdepsGeneratorSingleton{} } type ccdepsGeneratorSingleton struct { outputPath android.Path } var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil) const ( // Environment variables used to control the behavior of this singleton. envVariableCollectCCDeps = "SOONG_COLLECT_CC_DEPS" ccdepsJsonFileName = "module_bp_cc_deps.json" cClang = "clang" cppClang = "clang++" ) type ccIdeInfo struct { Path []string `json:"path,omitempty"` Srcs []string `json:"srcs,omitempty"` Global_Common_Flags ccParameters `json:"global_common_flags,omitempty"` Local_Common_Flags ccParameters `json:"local_common_flags,omitempty"` Global_C_flags ccParameters `json:"global_c_flags,omitempty"` Local_C_flags ccParameters `json:"local_c_flags,omitempty"` Global_C_only_flags ccParameters `json:"global_c_only_flags,omitempty"` Local_C_only_flags ccParameters `json:"local_c_only_flags,omitempty"` Global_Cpp_flags ccParameters `json:"global_cpp_flags,omitempty"` Local_Cpp_flags ccParameters `json:"local_cpp_flags,omitempty"` System_include_flags ccParameters `json:"system_include_flags,omitempty"` Module_name string `json:"module_name,omitempty"` } type ccParameters struct { HeaderSearchPath []string `json:"header_search_path,omitempty"` SystemHeaderSearchPath []string `json:"system_search_path,omitempty"` FlagParameters []string `json:"flag,omitempty"` SysRoot string `json:"system_root,omitempty"` RelativeFilePathFlags map[string]string `json:"relative_file_path,omitempty"` } type ccMapIdeInfos map[string]ccIdeInfo type ccDeps struct { C_clang string `json:"clang,omitempty"` Cpp_clang string `json:"clang++,omitempty"` Modules ccMapIdeInfos `json:"modules,omitempty"` } func (c *ccdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { if !ctx.Config().IsEnvTrue(envVariableCollectCCDeps) { return } moduleDeps := ccDeps{} moduleInfos := map[string]ccIdeInfo{} // Track which projects have already had CMakeLists.txt generated to keep the first // variant for each project. seenProjects := map[string]bool{} pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/") moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang) moduleDeps.Cpp_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cppClang) ctx.VisitAllModules(func(module android.Module) { if ccModule, ok := module.(*Module); ok { if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok { generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos) } } }) moduleDeps.Modules = moduleInfos ccfpath := android.PathForOutput(ctx, ccdepsJsonFileName) err := createJsonFile(moduleDeps, ccfpath) if err != nil { ctx.Errorf(err.Error()) } c.outputPath = ccfpath // This is necessary to satisfy the dangling rules check as this file is written by Soong rather than a rule. ctx.Build(pctx, android.BuildParams{ Rule: android.Touch, Output: ccfpath, }) } func (c *ccdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) { if c.outputPath == nil { return } ctx.DistForGoal("general-tests", c.outputPath) } func parseCompilerCCParameters(ctx android.SingletonContext, params []string) ccParameters { compilerParams := ccParameters{} cparams := []string{} for _, param := range params { param, _ = evalVariable(ctx, param) cparams = append(cparams, param) } // Soong does not guarantee that each flag will be in an individual string. e.g: The // input received could be: // params = {"-isystem", "path/to/system"} // or it could be // params = {"-isystem path/to/system"} // To normalize the input, we split all strings with the "space" character and consolidate // all tokens into a flattened parameters list cparams = normalizeParameters(cparams) for i := 0; i < len(cparams); i++ { param := cparams[i] if param == "" { continue } switch categorizeParameter(param) { case headerSearchPath: compilerParams.HeaderSearchPath = append(compilerParams.HeaderSearchPath, strings.TrimPrefix(param, "-I")) case systemHeaderSearchPath: if i < len(cparams)-1 { compilerParams.SystemHeaderSearchPath = append(compilerParams.SystemHeaderSearchPath, cparams[i+1]) } i = i + 1 case flag: c := cleanupParameter(param) compilerParams.FlagParameters = append(compilerParams.FlagParameters, c) case systemRoot: if i < len(cparams)-1 { compilerParams.SysRoot = cparams[i+1] } i = i + 1 case relativeFilePathFlag: flagComponents := strings.Split(param, "=") if len(flagComponents) == 2 { if compilerParams.RelativeFilePathFlags == nil { compilerParams.RelativeFilePathFlags = map[string]string{} } compilerParams.RelativeFilePathFlags[flagComponents[0]] = flagComponents[1] } } } return compilerParams } func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface, ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) { srcs := compiledModule.Srcs() if len(srcs) == 0 { return } // Only keep the DeviceArch variant module. if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name { return } clionProjectLocation := getCMakeListsForModule(ccModule, ctx) if seenProjects[clionProjectLocation] { return } seenProjects[clionProjectLocation] = true name := ccModule.ModuleBase.Name() dpInfo := moduleInfos[name] dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule))) dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...) dpInfo.Path = android.FirstUniqueStrings(dpInfo.Path) dpInfo.Srcs = android.FirstUniqueStrings(dpInfo.Srcs) dpInfo.Global_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CommonFlags) dpInfo.Local_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CommonFlags) dpInfo.Global_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CFlags) dpInfo.Local_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CFlags) dpInfo.Global_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.ConlyFlags) dpInfo.Local_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.ConlyFlags) dpInfo.Global_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CppFlags) dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags) dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags) dpInfo.Module_name = name moduleInfos[name] = dpInfo } type Deal struct { Name string ideInfo ccIdeInfo } type Deals []Deal // Ensure it satisfies sort.Interface func (d Deals) Len() int { return len(d) } func (d Deals) Less(i, j int) bool { return d[i].Name < d[j].Name } func (d Deals) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func sortMap(moduleInfos map[string]ccIdeInfo) map[string]ccIdeInfo { var deals Deals for k, v := range moduleInfos { deals = append(deals, Deal{k, v}) } sort.Sort(deals) m := map[string]ccIdeInfo{} for _, d := range deals { m[d.Name] = d.ideInfo } return m } func createJsonFile(moduleDeps ccDeps, ccfpath android.WritablePath) error { buf, err := json.MarshalIndent(moduleDeps, "", "\t") if err != nil { return fmt.Errorf("JSON marshal of cc deps failed: %s", err) } err = android.WriteFileToOutputDir(ccfpath, buf, 0666) if err != nil { return fmt.Errorf("Writing cc deps to %s failed: %s", ccfpath.String(), err) } return nil }