| // Copyright 2017 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. |
| |
| // This file implements the logic of bpfix and also provides a programmatic interface |
| |
| package bpfix |
| |
| import ( |
| "bytes" |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "reflect" |
| "sort" |
| "strings" |
| |
| "github.com/google/blueprint/parser" |
| "github.com/google/blueprint/pathtools" |
| ) |
| |
| // Reformat takes a blueprint file as a string and returns a formatted version |
| func Reformat(input string) (string, error) { |
| tree, err := parse("<string>", bytes.NewBufferString(input)) |
| if err != nil { |
| return "", err |
| } |
| |
| res, err := parser.Print(tree) |
| if err != nil { |
| return "", err |
| } |
| |
| return string(res), nil |
| } |
| |
| // A FixRequest specifies the details of which fixes to apply to an individual file |
| // A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go |
| type FixRequest struct { |
| steps []FixStep |
| } |
| type FixStepsExtension struct { |
| Name string |
| Steps []FixStep |
| } |
| |
| type FixStep struct { |
| Name string |
| Fix func(f *Fixer) error |
| } |
| |
| var fixStepsExtensions = []*FixStepsExtension(nil) |
| |
| func RegisterFixStepExtension(extension *FixStepsExtension) { |
| fixStepsExtensions = append(fixStepsExtensions, extension) |
| } |
| |
| var fixSteps = []FixStep{ |
| { |
| Name: "simplifyKnownRedundantVariables", |
| Fix: runPatchListMod(simplifyKnownPropertiesDuplicatingEachOther), |
| }, |
| { |
| Name: "rewriteIncorrectAndroidmkPrebuilts", |
| Fix: rewriteIncorrectAndroidmkPrebuilts, |
| }, |
| { |
| Name: "rewriteCtsModuleTypes", |
| Fix: rewriteCtsModuleTypes, |
| }, |
| { |
| Name: "rewriteIncorrectAndroidmkAndroidLibraries", |
| Fix: rewriteIncorrectAndroidmkAndroidLibraries, |
| }, |
| { |
| Name: "rewriteTestModuleTypes", |
| Fix: rewriteTestModuleTypes, |
| }, |
| { |
| Name: "rewriteAndroidmkJavaLibs", |
| Fix: rewriteAndroidmkJavaLibs, |
| }, |
| { |
| Name: "rewriteJavaStaticLibs", |
| Fix: rewriteJavaStaticLibs, |
| }, |
| { |
| Name: "rewritePrebuiltEtc", |
| Fix: rewriteAndroidmkPrebuiltEtc, |
| }, |
| { |
| Name: "mergeMatchingModuleProperties", |
| Fix: runPatchListMod(mergeMatchingModuleProperties), |
| }, |
| { |
| Name: "reorderCommonProperties", |
| Fix: runPatchListMod(reorderCommonProperties), |
| }, |
| { |
| Name: "removeTags", |
| Fix: runPatchListMod(removeTags), |
| }, |
| { |
| Name: "rewriteAndroidTest", |
| Fix: rewriteAndroidTest, |
| }, |
| { |
| Name: "rewriteAndroidAppImport", |
| Fix: rewriteAndroidAppImport, |
| }, |
| { |
| Name: "removeEmptyLibDependencies", |
| Fix: removeEmptyLibDependencies, |
| }, |
| { |
| Name: "removeHidlInterfaceTypes", |
| Fix: removeHidlInterfaceTypes, |
| }, |
| { |
| Name: "removeSoongConfigBoolVariable", |
| Fix: removeSoongConfigBoolVariable, |
| }, |
| { |
| Name: "removePdkProperty", |
| Fix: runPatchListMod(removeObsoleteProperty("product_variables.pdk")), |
| }, |
| { |
| Name: "removeScudoProperty", |
| Fix: runPatchListMod(removeObsoleteProperty("sanitize.scudo")), |
| }, |
| { |
| Name: "removeAndroidLicenseKinds", |
| Fix: runPatchListMod(removeIncorrectProperties("android_license_kinds")), |
| }, |
| { |
| Name: "removeAndroidLicenseConditions", |
| Fix: runPatchListMod(removeIncorrectProperties("android_license_conditions")), |
| }, |
| { |
| Name: "removeAndroidLicenseFiles", |
| Fix: runPatchListMod(removeIncorrectProperties("android_license_files")), |
| }, |
| { |
| Name: "formatFlagProperties", |
| Fix: runPatchListMod(formatFlagProperties), |
| }, |
| { |
| Name: "removeResourcesAndAssetsIfDefault", |
| Fix: removeResourceAndAssetsIfDefault, |
| }, |
| } |
| |
| // for fix that only need to run once |
| var fixStepsOnce = []FixStep{ |
| { |
| Name: "haveSameLicense", |
| Fix: haveSameLicense, |
| }, |
| { |
| Name: "rewriteLicenseProperties", |
| Fix: runPatchListMod(rewriteLicenseProperty(nil, "")), |
| }, |
| } |
| |
| func NewFixRequest() FixRequest { |
| return FixRequest{} |
| } |
| |
| func (r FixRequest) AddAll() (result FixRequest) { |
| result.steps = append([]FixStep(nil), r.steps...) |
| result.steps = append(result.steps, fixSteps...) |
| for _, extension := range fixStepsExtensions { |
| result.steps = append(result.steps, extension.Steps...) |
| } |
| return result |
| } |
| |
| func (r FixRequest) AddBase() (result FixRequest) { |
| result.steps = append([]FixStep(nil), r.steps...) |
| result.steps = append(result.steps, fixSteps...) |
| return result |
| } |
| |
| func (r FixRequest) AddMatchingExtensions(pattern string) (result FixRequest) { |
| result.steps = append([]FixStep(nil), r.steps...) |
| for _, extension := range fixStepsExtensions { |
| if match, _ := filepath.Match(pattern, extension.Name); match { |
| result.steps = append(result.steps, extension.Steps...) |
| } |
| } |
| return result |
| } |
| |
| type Fixer struct { |
| tree *parser.File |
| } |
| |
| func (f Fixer) Tree() *parser.File { |
| return f.tree |
| } |
| |
| func NewFixer(tree *parser.File) *Fixer { |
| fixer := &Fixer{tree} |
| |
| // make a copy of the tree |
| fixer.reparse() |
| |
| return fixer |
| } |
| |
| // Fix repeatedly applies the fixes listed in the given FixRequest to the given File |
| // until there is no fix that affects the tree |
| func (f *Fixer) Fix(config FixRequest) (*parser.File, error) { |
| prevIdentifier, err := f.fingerprint() |
| if err != nil { |
| return nil, err |
| } |
| |
| // run fix that is expected to run once first |
| configOnce := NewFixRequest() |
| configOnce.steps = append(configOnce.steps, fixStepsOnce...) |
| if len(configOnce.steps) > 0 { |
| err = f.fixTreeOnce(configOnce) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| maxNumIterations := 20 |
| i := 0 |
| for { |
| err = f.fixTreeOnce(config) |
| newIdentifier, err := f.fingerprint() |
| if err != nil { |
| return nil, err |
| } |
| if bytes.Equal(newIdentifier, prevIdentifier) { |
| break |
| } |
| prevIdentifier = newIdentifier |
| // any errors from a previous iteration generally get thrown away and overwritten by errors on the next iteration |
| |
| // detect infinite loop |
| i++ |
| if i >= maxNumIterations { |
| return nil, fmt.Errorf("Applied fixes %d times and yet the tree continued to change. Is there an infinite loop?", i) |
| } |
| } |
| return f.tree, err |
| } |
| |
| // returns a unique identifier for the given tree that can be used to determine whether the tree changed |
| func (f *Fixer) fingerprint() (fingerprint []byte, err error) { |
| bytes, err := parser.Print(f.tree) |
| if err != nil { |
| return nil, err |
| } |
| return bytes, nil |
| } |
| |
| func (f *Fixer) reparse() ([]byte, error) { |
| buf, err := parser.Print(f.tree) |
| if err != nil { |
| return nil, err |
| } |
| newTree, err := parse(f.tree.Name, bytes.NewReader(buf)) |
| if err != nil { |
| return nil, err |
| } |
| f.tree = newTree |
| return buf, nil |
| } |
| |
| func parse(name string, r io.Reader) (*parser.File, error) { |
| tree, errs := parser.Parse(name, r, parser.NewScope(nil)) |
| if errs != nil { |
| s := "parse error: " |
| for _, err := range errs { |
| s += "\n" + err.Error() |
| } |
| return nil, errors.New(s) |
| } |
| return tree, nil |
| } |
| |
| func (f *Fixer) fixTreeOnce(config FixRequest) error { |
| for _, fix := range config.steps { |
| err := fix.Fix(f) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func simplifyKnownPropertiesDuplicatingEachOther(mod *parser.Module, buf []byte, patchList *parser.PatchList) error { |
| // remove from local_include_dirs anything in export_include_dirs |
| return removeMatchingModuleListProperties(mod, patchList, |
| "export_include_dirs", "local_include_dirs") |
| } |
| |
| func rewriteIncorrectAndroidmkPrebuilts(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| if mod.Type != "java_import" { |
| continue |
| } |
| host, _ := getLiteralBoolPropertyValue(mod, "host") |
| if host { |
| mod.Type = "java_import_host" |
| removeProperty(mod, "host") |
| } |
| srcs, ok := getLiteralListProperty(mod, "srcs") |
| if !ok { |
| continue |
| } |
| if len(srcs.Values) == 0 { |
| continue |
| } |
| src, ok := srcs.Values[0].(*parser.String) |
| if !ok { |
| continue |
| } |
| switch filepath.Ext(src.Value) { |
| case ".jar": |
| renameProperty(mod, "srcs", "jars") |
| |
| case ".aar": |
| renameProperty(mod, "srcs", "aars") |
| mod.Type = "android_library_import" |
| |
| // An android_library_import doesn't get installed, so setting "installable = false" isn't supported |
| removeProperty(mod, "installable") |
| } |
| } |
| |
| return nil |
| } |
| |
| func rewriteCtsModuleTypes(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if mod.Type != "cts_support_package" && mod.Type != "cts_package" && |
| mod.Type != "cts_target_java_library" && |
| mod.Type != "cts_host_java_library" { |
| |
| continue |
| } |
| |
| var defStr string |
| switch mod.Type { |
| case "cts_support_package": |
| mod.Type = "android_test_helper_app" |
| defStr = "cts_support_defaults" |
| case "cts_package": |
| mod.Type = "android_test" |
| defStr = "cts_defaults" |
| case "cts_target_java_library": |
| mod.Type = "java_library" |
| defStr = "cts_defaults" |
| case "cts_host_java_library": |
| mod.Type = "java_library_host" |
| defStr = "cts_defaults" |
| } |
| |
| defaults := &parser.Property{ |
| Name: "defaults", |
| Value: &parser.List{ |
| Values: []parser.Expression{ |
| &parser.String{ |
| Value: defStr, |
| }, |
| }, |
| }, |
| } |
| mod.Properties = append(mod.Properties, defaults) |
| } |
| |
| return nil |
| } |
| |
| func rewriteIncorrectAndroidmkAndroidLibraries(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") { |
| continue |
| } |
| |
| hasAndroidLibraries := hasNonEmptyLiteralListProperty(mod, "android_libs") |
| hasStaticAndroidLibraries := hasNonEmptyLiteralListProperty(mod, "android_static_libs") |
| hasResourceDirs := hasNonEmptyLiteralListProperty(mod, "resource_dirs") |
| |
| if hasAndroidLibraries || hasStaticAndroidLibraries || hasResourceDirs { |
| if mod.Type == "java_library_static" || mod.Type == "java_library" { |
| mod.Type = "android_library" |
| } |
| } |
| |
| if mod.Type == "java_import" && !hasStaticAndroidLibraries { |
| removeProperty(mod, "android_static_libs") |
| } |
| |
| // These may conflict with existing libs and static_libs properties, but the |
| // mergeMatchingModuleProperties pass will fix it. |
| renameProperty(mod, "shared_libs", "libs") |
| renameProperty(mod, "android_libs", "libs") |
| renameProperty(mod, "android_static_libs", "static_libs") |
| } |
| |
| return nil |
| } |
| |
| // rewriteTestModuleTypes looks for modules that are identifiable as tests but for which Make doesn't have a separate |
| // module class, and moves them to the appropriate Soong module type. |
| func rewriteTestModuleTypes(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") && mod.Type != "cc_binary" { |
| continue |
| } |
| |
| hasInstrumentationFor := hasNonEmptyLiteralStringProperty(mod, "instrumentation_for") |
| tags, _ := getLiteralListPropertyValue(mod, "tags") |
| |
| var hasTestsTag bool |
| for _, tag := range tags { |
| if tag == "tests" { |
| hasTestsTag = true |
| } |
| } |
| |
| isTest := hasInstrumentationFor || hasTestsTag |
| |
| if isTest { |
| switch mod.Type { |
| case "android_app": |
| mod.Type = "android_test" |
| case "android_app_import": |
| mod.Type = "android_test_import" |
| case "java_library", "java_library_installable": |
| mod.Type = "java_test" |
| case "java_library_host": |
| mod.Type = "java_test_host" |
| } |
| } |
| |
| // when a cc_binary module has a nonempty test_suites field, modify the type to cc_test |
| if mod.Type == "cc_binary" { |
| hasTestSuites := hasNonEmptyLiteralListProperty(mod, "test_suites") |
| if hasTestSuites { |
| mod.Type = "cc_test" |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // rewriteJavaStaticLibs rewrites java_library_static into java_library |
| func rewriteJavaStaticLibs(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if mod.Type == "java_library_static" { |
| mod.Type = "java_library" |
| } |
| } |
| |
| return nil |
| } |
| |
| // rewriteAndroidmkJavaLibs rewrites java_library_installable into java_library plus installable: true |
| func rewriteAndroidmkJavaLibs(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if mod.Type != "java_library_installable" { |
| continue |
| } |
| |
| mod.Type = "java_library" |
| |
| _, hasInstallable := mod.GetProperty("installable") |
| if !hasInstallable { |
| prop := &parser.Property{ |
| Name: "installable", |
| Value: &parser.Bool{ |
| Value: true, |
| }, |
| } |
| mod.Properties = append(mod.Properties, prop) |
| } |
| } |
| |
| return nil |
| } |
| |
| // Helper function to get the value of a string-valued property in a given compound property. |
| func getStringProperty(prop *parser.Property, fieldName string) string { |
| if propsAsMap, ok := prop.Value.(*parser.Map); ok { |
| for _, propField := range propsAsMap.Properties { |
| if fieldName == propField.Name { |
| if propFieldAsString, ok := propField.Value.(*parser.String); ok { |
| return propFieldAsString.Value |
| } else { |
| return "" |
| } |
| } |
| } |
| } |
| return "" |
| } |
| |
| // Set the value of the given attribute to the error message |
| func indicateAttributeError(mod *parser.Module, attributeName string, format string, a ...interface{}) error { |
| msg := fmt.Sprintf(format, a...) |
| mod.Properties = append(mod.Properties, &parser.Property{ |
| Name: attributeName, |
| Value: &parser.String{Value: "ERROR: " + msg}, |
| }) |
| return errors.New(msg) |
| } |
| |
| // If a variable is LOCAL_MODULE, get its value from the 'name' attribute. |
| // This handles the statement |
| // LOCAL_SRC_FILES := $(LOCAL_MODULE) |
| // which occurs often. |
| func resolveLocalModule(mod *parser.Module, val parser.Expression) parser.Expression { |
| if varLocalName, ok := val.(*parser.Variable); ok { |
| if varLocalName.Name == "LOCAL_MODULE" { |
| if v, ok := getLiteralStringProperty(mod, "name"); ok { |
| return v |
| } |
| } |
| } |
| return val |
| } |
| |
| // etcPrebuiltModuleUpdate contains information on updating certain parts of a defined module such as: |
| // * changing the module type from prebuilt_etc to a different one |
| // * stripping the prefix of the install path based on the module type |
| // * appending additional boolean properties to the prebuilt module |
| type etcPrebuiltModuleUpdate struct { |
| // The prefix of the install path defined in local_module_path. The prefix is removed from local_module_path |
| // before setting the 'filename' attribute. |
| prefix string |
| |
| // There is only one prebuilt module type in makefiles. In Soong, there are multiple versions of |
| // prebuilts based on local_module_path. By default, it is "prebuilt_etc" if modType is blank. An |
| // example is if the local_module_path contains $(TARGET_OUT)/usr/share, the module type is |
| // considered as prebuilt_usr_share. |
| modType string |
| |
| // Additional boolean attributes to be added in the prebuilt module. Each added boolean attribute |
| // has a value of true. |
| flags []string |
| } |
| |
| func (f etcPrebuiltModuleUpdate) update(m *parser.Module, path string) bool { |
| updated := false |
| if path == f.prefix { |
| updated = true |
| } else if trimmedPath := strings.TrimPrefix(path, f.prefix+"/"); trimmedPath != path { |
| m.Properties = append(m.Properties, &parser.Property{ |
| Name: "relative_install_path", |
| Value: &parser.String{Value: trimmedPath}, |
| }) |
| updated = true |
| } |
| if updated { |
| for _, flag := range f.flags { |
| m.Properties = append(m.Properties, &parser.Property{Name: flag, Value: &parser.Bool{Value: true, Token: "true"}}) |
| } |
| if f.modType != "" { |
| m.Type = f.modType |
| } |
| } |
| return updated |
| } |
| |
| var localModuleUpdate = map[string][]etcPrebuiltModuleUpdate{ |
| "HOST_OUT": { |
| {prefix: "/etc", modType: "prebuilt_etc_host"}, |
| {prefix: "/usr/share", modType: "prebuilt_usr_share_host"}, |
| {prefix: "", modType: "prebuilt_root_host"}, |
| }, |
| "PRODUCT_OUT": {{prefix: "/system/etc"}, {prefix: "/vendor/etc", flags: []string{"proprietary"}}}, |
| "TARGET_OUT": {{prefix: "/usr/share", modType: "prebuilt_usr_share"}, {prefix: "/fonts", modType: "prebuilt_font"}, |
| {prefix: "/etc/firmware", modType: "prebuilt_firmware"}, {prefix: "/vendor/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}}, |
| {prefix: "/etc"}}, |
| "TARGET_OUT_ETC": {{prefix: "/firmware", modType: "prebuilt_firmware"}, {prefix: ""}}, |
| "TARGET_OUT_PRODUCT": {{prefix: "/etc", flags: []string{"product_specific"}}, {prefix: "/fonts", modType: "prebuilt_font", flags: []string{"product_specific"}}}, |
| "TARGET_OUT_PRODUCT_ETC": {{prefix: "", flags: []string{"product_specific"}}}, |
| "TARGET_OUT_ODM": {{prefix: "/etc", flags: []string{"device_specific"}}}, |
| "TARGET_OUT_SYSTEM_EXT": {{prefix: "/etc", flags: []string{"system_ext_specific"}}}, |
| "TARGET_OUT_SYSTEM_EXT_ETC": {{prefix: "", flags: []string{"system_ext_specific"}}}, |
| "TARGET_OUT_VENDOR": {{prefix: "/etc", flags: []string{"proprietary"}}, {prefix: "/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}}}, |
| "TARGET_OUT_VENDOR_ETC": {{prefix: "", flags: []string{"proprietary"}}}, |
| "TARGET_RECOVERY_ROOT_OUT": {{prefix: "/system/etc", flags: []string{"recovery"}}}, |
| } |
| |
| // rewriteAndroidPrebuiltEtc fixes prebuilt_etc rule |
| func rewriteAndroidmkPrebuiltEtc(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| if mod.Type != "prebuilt_etc" && mod.Type != "prebuilt_etc_host" { |
| continue |
| } |
| |
| // 'srcs' --> 'src' conversion |
| convertToSingleSource(mod, "src") |
| |
| renameProperty(mod, "sub_dir", "relative_install_path") |
| |
| // The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute |
| // 'local_module_path'. Analyze its contents and create the correct sub_dir:, |
| // filename: and boolean attributes combination |
| const local_module_path = "local_module_path" |
| if prop_local_module_path, ok := mod.GetProperty(local_module_path); ok { |
| removeProperty(mod, local_module_path) |
| prefixVariableName := getStringProperty(prop_local_module_path, "var") |
| if moduleUpdates, ok := localModuleUpdate[prefixVariableName]; ok { |
| path := getStringProperty(prop_local_module_path, "fixed") |
| updated := false |
| for i := 0; i < len(moduleUpdates) && !updated; i++ { |
| updated = moduleUpdates[i].update(mod, path) |
| } |
| if !updated { |
| expectedPrefices := "" |
| sep := "" |
| for _, moduleUpdate := range moduleUpdates { |
| expectedPrefices += sep |
| sep = ", " |
| expectedPrefices += moduleUpdate.prefix |
| } |
| return indicateAttributeError(mod, "filename", |
| "LOCAL_MODULE_PATH value under $(%s) should start with %s", prefixVariableName, expectedPrefices) |
| } |
| } else { |
| return indicateAttributeError(mod, "filename", "Cannot handle $(%s) for the prebuilt_etc", prefixVariableName) |
| } |
| } |
| } |
| return nil |
| } |
| |
| func rewriteAndroidTest(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| // The definition is not a module. |
| continue |
| } |
| if mod.Type != "android_test" && mod.Type != "android_test_helper_app" { |
| // The module is not an android_test or android_test_helper_app. |
| continue |
| } |
| // The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute |
| // 'local_module_path'. For the android_test module, it should be $(TARGET_OUT_DATA_APPS), |
| // that is, `local_module_path: { var: "TARGET_OUT_DATA_APPS"}` |
| // 1. if the `key: val` pair matches, (key is `local_module_path`, |
| // and val is `{ var: "TARGET_OUT_DATA_APPS"}`), this property is removed; |
| // 2. o/w, an error msg is thrown. |
| const local_module_path = "local_module_path" |
| if prop_local_module_path, ok := mod.GetProperty(local_module_path); ok { |
| removeProperty(mod, local_module_path) |
| prefixVariableName := getStringProperty(prop_local_module_path, "var") |
| path := getStringProperty(prop_local_module_path, "fixed") |
| if prefixVariableName == "TARGET_OUT_DATA_APPS" && path == "" { |
| continue |
| } |
| return indicateAttributeError(mod, "filename", |
| "Only LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) is allowed for the %s", mod.Type) |
| } |
| } |
| return nil |
| } |
| |
| func rewriteAndroidAppImport(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !(ok && mod.Type == "android_app_import") { |
| continue |
| } |
| // 'srcs' --> 'apk' conversion |
| convertToSingleSource(mod, "apk") |
| // Handle special certificate value, "PRESIGNED". |
| if cert, ok := mod.GetProperty("certificate"); ok { |
| if certStr, ok := cert.Value.(*parser.String); ok { |
| if certStr.Value == "PRESIGNED" { |
| removeProperty(mod, "certificate") |
| prop := &parser.Property{ |
| Name: "presigned", |
| Value: &parser.Bool{ |
| Value: true, |
| }, |
| } |
| mod.Properties = append(mod.Properties, prop) |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| func RewriteRuntimeResourceOverlay(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !(ok && mod.Type == "runtime_resource_overlay") { |
| continue |
| } |
| // runtime_resource_overlays are always product specific in Make. |
| if _, ok := mod.GetProperty("product_specific"); !ok { |
| prop := &parser.Property{ |
| Name: "product_specific", |
| Value: &parser.Bool{ |
| Value: true, |
| }, |
| } |
| mod.Properties = append(mod.Properties, prop) |
| } |
| } |
| return nil |
| } |
| |
| // Removes library dependencies which are empty (and restricted from usage in Soong) |
| func removeEmptyLibDependencies(f *Fixer) error { |
| emptyLibraries := []string{ |
| "libhidltransport", |
| "libhwbinder", |
| } |
| relevantFields := []string{ |
| "export_shared_lib_headers", |
| "export_static_lib_headers", |
| "static_libs", |
| "whole_static_libs", |
| "shared_libs", |
| } |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| for _, field := range relevantFields { |
| listValue, ok := getLiteralListProperty(mod, field) |
| if !ok { |
| continue |
| } |
| newValues := []parser.Expression{} |
| for _, v := range listValue.Values { |
| stringValue, ok := v.(*parser.String) |
| if !ok { |
| return fmt.Errorf("Expecting string for %s.%s fields", mod.Type, field) |
| } |
| if inList(stringValue.Value, emptyLibraries) { |
| continue |
| } |
| newValues = append(newValues, stringValue) |
| } |
| if len(newValues) == 0 && len(listValue.Values) != 0 { |
| removeProperty(mod, field) |
| } else { |
| listValue.Values = newValues |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Removes hidl_interface 'types' which are no longer needed |
| func removeHidlInterfaceTypes(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !(ok && mod.Type == "hidl_interface") { |
| continue |
| } |
| removeProperty(mod, "types") |
| } |
| return nil |
| } |
| |
| func removeSoongConfigBoolVariable(f *Fixer) error { |
| found := map[string]bool{} |
| newDefs := make([]parser.Definition, 0, len(f.tree.Defs)) |
| for _, def := range f.tree.Defs { |
| if mod, ok := def.(*parser.Module); ok && mod.Type == "soong_config_bool_variable" { |
| if name, ok := getLiteralStringPropertyValue(mod, "name"); ok { |
| found[name] = true |
| } else { |
| return fmt.Errorf("Found soong_config_bool_variable without a name") |
| } |
| } else { |
| newDefs = append(newDefs, def) |
| } |
| } |
| f.tree.Defs = newDefs |
| |
| if len(found) == 0 { |
| return nil |
| } |
| |
| return runPatchListMod(func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error { |
| if mod.Type != "soong_config_module_type" { |
| return nil |
| } |
| |
| variables, ok := getLiteralListProperty(mod, "variables") |
| if !ok { |
| return nil |
| } |
| |
| boolValues := strings.Builder{} |
| empty := true |
| for _, item := range variables.Values { |
| nameValue, ok := item.(*parser.String) |
| if !ok { |
| empty = false |
| continue |
| } |
| if found[nameValue.Value] { |
| patchList.Add(item.Pos().Offset, item.End().Offset+2, "") |
| |
| boolValues.WriteString(`"`) |
| boolValues.WriteString(nameValue.Value) |
| boolValues.WriteString(`",`) |
| } else { |
| empty = false |
| } |
| } |
| if empty { |
| *patchList = parser.PatchList{} |
| |
| prop, _ := mod.GetProperty("variables") |
| patchList.Add(prop.Pos().Offset, prop.End().Offset+2, "") |
| } |
| if boolValues.Len() == 0 { |
| return nil |
| } |
| |
| boolVariables, ok := getLiteralListProperty(mod, "bool_variables") |
| if ok { |
| patchList.Add(boolVariables.RBracePos.Offset, boolVariables.RBracePos.Offset, ","+boolValues.String()) |
| } else { |
| patchList.Add(variables.RBracePos.Offset+2, variables.RBracePos.Offset+2, |
| fmt.Sprintf(`bool_variables: [%s],`, boolValues.String())) |
| } |
| |
| return nil |
| })(f) |
| |
| return nil |
| } |
| |
| func removeResourceAndAssetsIfDefault(f *Fixer) error { |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| resourceDirList, resourceDirFound := getLiteralListPropertyValue(mod, "resource_dirs") |
| if resourceDirFound && len(resourceDirList) == 1 && resourceDirList[0] == "res" { |
| removeProperty(mod, "resource_dirs") |
| } |
| assetDirList, assetDirFound := getLiteralListPropertyValue(mod, "asset_dirs") |
| if assetDirFound && len(assetDirList) == 1 && assetDirList[0] == "assets" { |
| removeProperty(mod, "asset_dirs") |
| } |
| } |
| return nil |
| } |
| |
| // Converts the default source list property, 'srcs', to a single source property with a given name. |
| // "LOCAL_MODULE" reference is also resolved during the conversion process. |
| func convertToSingleSource(mod *parser.Module, srcPropertyName string) { |
| if srcs, ok := mod.GetProperty("srcs"); ok { |
| if srcList, ok := srcs.Value.(*parser.List); ok { |
| removeProperty(mod, "srcs") |
| if len(srcList.Values) == 1 { |
| mod.Properties = append(mod.Properties, |
| &parser.Property{ |
| Name: srcPropertyName, |
| NamePos: srcs.NamePos, |
| ColonPos: srcs.ColonPos, |
| Value: resolveLocalModule(mod, srcList.Values[0])}) |
| } else if len(srcList.Values) > 1 { |
| indicateAttributeError(mod, srcPropertyName, "LOCAL_SRC_FILES should contain at most one item") |
| } |
| } else if _, ok = srcs.Value.(*parser.Variable); ok { |
| removeProperty(mod, "srcs") |
| mod.Properties = append(mod.Properties, |
| &parser.Property{Name: srcPropertyName, |
| NamePos: srcs.NamePos, |
| ColonPos: srcs.ColonPos, |
| Value: resolveLocalModule(mod, srcs.Value)}) |
| } else { |
| renameProperty(mod, "srcs", "apk") |
| } |
| } |
| } |
| |
| type patchListModFunction func(*parser.Module, []byte, *parser.PatchList) error |
| |
| func runPatchListMod(modFunc patchListModFunction) func(*Fixer) error { |
| return func(f *Fixer) error { |
| // Make sure all the offsets are accurate |
| buf, err := f.reparse() |
| if err != nil { |
| return err |
| } |
| |
| var patchlist parser.PatchList |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| |
| err := modFunc(mod, buf, &patchlist) |
| if err != nil { |
| return err |
| } |
| } |
| |
| newBuf := new(bytes.Buffer) |
| err = patchlist.Apply(bytes.NewReader(buf), newBuf) |
| if err != nil { |
| return err |
| } |
| |
| // Save a copy of the buffer to print for errors below |
| bufCopy := append([]byte(nil), newBuf.Bytes()...) |
| |
| newTree, err := parse(f.tree.Name, newBuf) |
| if err != nil { |
| return fmt.Errorf("Failed to parse: %v\nBuffer:\n%s", err, string(bufCopy)) |
| } |
| |
| f.tree = newTree |
| |
| return nil |
| } |
| } |
| |
| var commonPropertyPriorities = []string{ |
| "name", |
| "defaults", |
| "device_supported", |
| "host_supported", |
| "installable", |
| } |
| |
| func reorderCommonProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error { |
| if len(mod.Properties) == 0 { |
| return nil |
| } |
| |
| pos := mod.LBracePos.Offset + 1 |
| stage := "" |
| |
| for _, name := range commonPropertyPriorities { |
| idx := propertyIndex(mod.Properties, name) |
| if idx == -1 { |
| continue |
| } |
| if idx == 0 { |
| err := patchlist.Add(pos, pos, stage) |
| if err != nil { |
| return err |
| } |
| stage = "" |
| |
| pos = mod.Properties[0].End().Offset + 1 |
| mod.Properties = mod.Properties[1:] |
| continue |
| } |
| |
| prop := mod.Properties[idx] |
| mod.Properties = append(mod.Properties[:idx], mod.Properties[idx+1:]...) |
| |
| stage += string(buf[prop.Pos().Offset : prop.End().Offset+1]) |
| |
| err := patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, "") |
| if err != nil { |
| return err |
| } |
| } |
| |
| if stage != "" { |
| err := patchlist.Add(pos, pos, stage) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func removeTags(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error { |
| prop, ok := mod.GetProperty("tags") |
| if !ok { |
| return nil |
| } |
| list, ok := prop.Value.(*parser.List) |
| if !ok { |
| return nil |
| } |
| |
| replaceStr := "" |
| |
| for _, item := range list.Values { |
| str, ok := item.(*parser.String) |
| if !ok { |
| replaceStr += fmt.Sprintf("// ERROR: Unable to parse tag %q\n", item) |
| continue |
| } |
| |
| switch str.Value { |
| case "optional": |
| continue |
| case "debug": |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // Add this module to PRODUCT_PACKAGES_DEBUG in your product file if you want to |
| // force installation for -userdebug and -eng builds. |
| ` |
| case "eng": |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // Add this module to PRODUCT_PACKAGES_ENG in your product file if you want to |
| // force installation for -eng builds. |
| ` |
| case "tests": |
| switch { |
| case strings.Contains(mod.Type, "cc_test"), |
| strings.Contains(mod.Type, "cc_library_static"), |
| strings.Contains(mod.Type, "java_test"), |
| mod.Type == "android_test", |
| mod.Type == "android_test_import": |
| continue |
| case strings.Contains(mod.Type, "cc_lib"): |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // To make a shared library only for tests, use the "cc_test_library" module |
| // type. If you don't use gtest, set "gtest: false". |
| ` |
| case strings.Contains(mod.Type, "cc_bin"): |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // For native test binaries, use the "cc_test" module type. Some differences: |
| // - If you don't use gtest, set "gtest: false" |
| // - Binaries will be installed into /data/nativetest[64]/<name>/<name> |
| // - Both 32 & 64 bit versions will be built (as appropriate) |
| ` |
| case strings.Contains(mod.Type, "java_lib"): |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // For JUnit or similar tests, use the "java_test" module type. A dependency on |
| // Junit will be added by default, if it is using some other runner, set "junit: false". |
| ` |
| case mod.Type == "android_app": |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // For JUnit or instrumentataion app tests, use the "android_test" module type. |
| ` |
| default: |
| replaceStr += `// WARNING: Module tags are not supported in Soong. |
| // In most cases, tests are now identified by their module type: |
| // cc_test, java_test, python_test |
| ` |
| } |
| default: |
| replaceStr += fmt.Sprintf("// WARNING: Unknown module tag %q\n", str.Value) |
| } |
| } |
| |
| return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, replaceStr) |
| } |
| |
| type propertyProvider interface { |
| GetProperty(string) (*parser.Property, bool) |
| RemoveProperty(string) bool |
| } |
| |
| func removeNestedProperty(mod *parser.Module, patchList *parser.PatchList, propName string) error { |
| propNames := strings.Split(propName, ".") |
| |
| var propProvider, toRemoveFrom propertyProvider |
| propProvider = mod |
| |
| var propToRemove *parser.Property |
| for i, name := range propNames { |
| p, ok := propProvider.GetProperty(name) |
| if !ok { |
| return nil |
| } |
| // if this is the inner most element, it's time to delete |
| if i == len(propNames)-1 { |
| if propToRemove == nil { |
| // if we cannot remove the properties that the current property is nested in, |
| // remove only the current property |
| propToRemove = p |
| toRemoveFrom = propProvider |
| } |
| |
| // remove the property from the list, in case we remove other properties in this list |
| toRemoveFrom.RemoveProperty(propToRemove.Name) |
| // only removing the property would leave blank line(s), remove with a patch |
| if err := patchList.Add(propToRemove.Pos().Offset, propToRemove.End().Offset+2, ""); err != nil { |
| return err |
| } |
| } else { |
| propMap, ok := p.Value.(*parser.Map) |
| if !ok { |
| return nil |
| } |
| if len(propMap.Properties) > 1 { |
| // if there are other properties in this struct, we need to keep this struct |
| toRemoveFrom = nil |
| propToRemove = nil |
| } else if propToRemove == nil { |
| // otherwise, we can remove the empty struct entirely |
| toRemoveFrom = propProvider |
| propToRemove = p |
| } |
| propProvider = propMap |
| } |
| } |
| |
| return nil |
| } |
| |
| func removeObsoleteProperty(propName string) patchListModFunction { |
| return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error { |
| return removeNestedProperty(mod, patchList, propName) |
| } |
| } |
| |
| func mergeMatchingModuleProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error { |
| return mergeMatchingProperties(&mod.Properties, buf, patchlist) |
| } |
| |
| func mergeMatchingProperties(properties *[]*parser.Property, buf []byte, patchlist *parser.PatchList) error { |
| seen := make(map[string]*parser.Property) |
| for i := 0; i < len(*properties); i++ { |
| property := (*properties)[i] |
| if prev, exists := seen[property.Name]; exists { |
| err := mergeProperties(prev, property, buf, patchlist) |
| if err != nil { |
| return err |
| } |
| *properties = append((*properties)[:i], (*properties)[i+1:]...) |
| } else { |
| seen[property.Name] = property |
| if mapProperty, ok := property.Value.(*parser.Map); ok { |
| err := mergeMatchingProperties(&mapProperty.Properties, buf, patchlist) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| func mergeProperties(a, b *parser.Property, buf []byte, patchlist *parser.PatchList) error { |
| // The value of one of the properties may be a variable reference with no type assigned |
| // Bail out in this case. Soong will notice duplicate entries and will tell to merge them. |
| if _, isVar := a.Value.(*parser.Variable); isVar { |
| return nil |
| } |
| if _, isVar := b.Value.(*parser.Variable); isVar { |
| return nil |
| } |
| if a.Value.Type() != b.Value.Type() { |
| return fmt.Errorf("type mismatch when merging properties %q: %s and %s", a.Name, a.Value.Type(), b.Value.Type()) |
| } |
| |
| switch a.Value.Type() { |
| case parser.StringType: |
| return fmt.Errorf("conflicting definitions of string property %q", a.Name) |
| case parser.ListType: |
| return mergeListProperties(a, b, buf, patchlist) |
| } |
| |
| return nil |
| } |
| |
| func mergeListProperties(a, b *parser.Property, buf []byte, patchlist *parser.PatchList) error { |
| aval, oka := a.Value.(*parser.List) |
| bval, okb := b.Value.(*parser.List) |
| if !oka || !okb { |
| // Merging expressions not supported yet |
| return nil |
| } |
| |
| s := string(buf[bval.LBracePos.Offset+1 : bval.RBracePos.Offset]) |
| if bval.LBracePos.Line != bval.RBracePos.Line { |
| if s[0] != '\n' { |
| panic("expected \n") |
| } |
| // If B is a multi line list, skip the first "\n" in case A already has a trailing "\n" |
| s = s[1:] |
| } |
| if aval.LBracePos.Line == aval.RBracePos.Line { |
| // A is a single line list with no trailing comma |
| if len(aval.Values) > 0 { |
| s = "," + s |
| } |
| } |
| |
| err := patchlist.Add(aval.RBracePos.Offset, aval.RBracePos.Offset, s) |
| if err != nil { |
| return err |
| } |
| err = patchlist.Add(b.NamePos.Offset, b.End().Offset+2, "") |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // removes from <items> every item present in <removals> |
| func filterExpressionList(patchList *parser.PatchList, items *parser.List, removals *parser.List) { |
| writeIndex := 0 |
| for _, item := range items.Values { |
| included := true |
| for _, removal := range removals.Values { |
| equal, err := parser.ExpressionsAreSame(item, removal) |
| if err != nil { |
| continue |
| } |
| if equal { |
| included = false |
| break |
| } |
| } |
| if included { |
| items.Values[writeIndex] = item |
| writeIndex++ |
| } else { |
| patchList.Add(item.Pos().Offset, item.End().Offset+2, "") |
| } |
| } |
| items.Values = items.Values[:writeIndex] |
| } |
| |
| // Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k] |
| func removeMatchingModuleListProperties(mod *parser.Module, patchList *parser.PatchList, canonicalName string, legacyName string) error { |
| legacyProp, ok := mod.GetProperty(legacyName) |
| if !ok { |
| return nil |
| } |
| legacyList, ok := legacyProp.Value.(*parser.List) |
| if !ok || len(legacyList.Values) == 0 { |
| return nil |
| } |
| canonicalList, ok := getLiteralListProperty(mod, canonicalName) |
| if !ok { |
| return nil |
| } |
| |
| localPatches := parser.PatchList{} |
| filterExpressionList(&localPatches, legacyList, canonicalList) |
| |
| if len(legacyList.Values) == 0 { |
| patchList.Add(legacyProp.Pos().Offset, legacyProp.End().Offset+2, "") |
| } else { |
| for _, p := range localPatches { |
| patchList.Add(p.Start, p.End, p.Replacement) |
| } |
| } |
| |
| return nil |
| } |
| |
| func hasNonEmptyLiteralListProperty(mod *parser.Module, name string) bool { |
| list, found := getLiteralListProperty(mod, name) |
| return found && len(list.Values) > 0 |
| } |
| |
| func hasNonEmptyLiteralStringProperty(mod *parser.Module, name string) bool { |
| s, found := getLiteralStringPropertyValue(mod, name) |
| return found && len(s) > 0 |
| } |
| |
| func getLiteralListProperty(mod *parser.Module, name string) (list *parser.List, found bool) { |
| prop, ok := mod.GetProperty(name) |
| if !ok { |
| return nil, false |
| } |
| list, ok = prop.Value.(*parser.List) |
| return list, ok |
| } |
| |
| func getLiteralListPropertyValue(mod *parser.Module, name string) (list []string, found bool) { |
| listValue, ok := getLiteralListProperty(mod, name) |
| if !ok { |
| return nil, false |
| } |
| for _, v := range listValue.Values { |
| stringValue, ok := v.(*parser.String) |
| if !ok { |
| return nil, false |
| } |
| list = append(list, stringValue.Value) |
| } |
| |
| return list, true |
| } |
| |
| func getLiteralStringProperty(mod *parser.Module, name string) (s *parser.String, found bool) { |
| prop, ok := mod.GetProperty(name) |
| if !ok { |
| return nil, false |
| } |
| s, ok = prop.Value.(*parser.String) |
| return s, ok |
| } |
| |
| func getLiteralStringPropertyValue(mod *parser.Module, name string) (s string, found bool) { |
| stringValue, ok := getLiteralStringProperty(mod, name) |
| if !ok { |
| return "", false |
| } |
| |
| return stringValue.Value, true |
| } |
| |
| func getLiteralBoolProperty(mod *parser.Module, name string) (b *parser.Bool, found bool) { |
| prop, ok := mod.GetProperty(name) |
| if !ok { |
| return nil, false |
| } |
| b, ok = prop.Value.(*parser.Bool) |
| return b, ok |
| } |
| |
| func getLiteralBoolPropertyValue(mod *parser.Module, name string) (s bool, found bool) { |
| boolValue, ok := getLiteralBoolProperty(mod, name) |
| if !ok { |
| return false, false |
| } |
| |
| return boolValue.Value, true |
| } |
| |
| func propertyIndex(props []*parser.Property, propertyName string) int { |
| for i, prop := range props { |
| if prop.Name == propertyName { |
| return i |
| } |
| } |
| return -1 |
| } |
| |
| func renameProperty(mod *parser.Module, from, to string) { |
| for _, prop := range mod.Properties { |
| if prop.Name == from { |
| prop.Name = to |
| } |
| } |
| } |
| |
| func removeProperty(mod *parser.Module, propertyName string) { |
| newList := make([]*parser.Property, 0, len(mod.Properties)) |
| for _, prop := range mod.Properties { |
| if prop.Name != propertyName { |
| newList = append(newList, prop) |
| } |
| } |
| mod.Properties = newList |
| } |
| |
| func inList(s string, list []string) bool { |
| for _, v := range list { |
| if s == v { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func formatFlagProperty(mod *parser.Module, field string, buf []byte, patchlist *parser.PatchList) error { |
| // the comment or empty lines in the value of the field are skipped |
| listValue, ok := getLiteralListProperty(mod, field) |
| if !ok { |
| // if do not find |
| return nil |
| } |
| for i := 0; i < len(listValue.Values); i++ { |
| curValue, ok := listValue.Values[i].(*parser.String) |
| if !ok { |
| return fmt.Errorf("Expecting string for %s.%s fields", mod.Type, field) |
| } |
| if !strings.HasPrefix(curValue.Value, "-") { |
| return fmt.Errorf("Expecting the string `%s` starting with '-'", curValue.Value) |
| } |
| if i+1 < len(listValue.Values) { |
| nextValue, ok := listValue.Values[i+1].(*parser.String) |
| if !ok { |
| return fmt.Errorf("Expecting string for %s.%s fields", mod.Type, field) |
| } |
| if !strings.HasPrefix(nextValue.Value, "-") { |
| // delete the line |
| err := patchlist.Add(curValue.Pos().Offset, curValue.End().Offset+2, "") |
| if err != nil { |
| return err |
| } |
| // replace the line |
| value := "\"" + curValue.Value + " " + nextValue.Value + "\"," |
| err = patchlist.Add(nextValue.Pos().Offset, nextValue.End().Offset+1, value) |
| if err != nil { |
| return err |
| } |
| // combined two lines to one |
| i++ |
| } |
| } |
| } |
| return nil |
| } |
| |
| func formatFlagProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error { |
| relevantFields := []string{ |
| // cc flags |
| "asflags", |
| "cflags", |
| "clang_asflags", |
| "clang_cflags", |
| "conlyflags", |
| "cppflags", |
| "ldflags", |
| "tidy_flags", |
| // java flags |
| "aaptflags", |
| "dxflags", |
| "javacflags", |
| "kotlincflags", |
| } |
| for _, field := range relevantFields { |
| err := formatFlagProperty(mod, field, buf, patchlist) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func rewriteLicenseProperty(fs pathtools.FileSystem, relativePath string) patchListModFunction { |
| return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error { |
| return rewriteLicenseProperties(mod, patchList, fs, relativePath) |
| } |
| } |
| |
| // rewrite the "android_license_kinds" and "android_license_files" properties to a package module |
| // (and a license module when needed). |
| func rewriteLicenseProperties(mod *parser.Module, patchList *parser.PatchList, fs pathtools.FileSystem, |
| relativePath string) error { |
| // if a package module has been added, no more action is needed. |
| for _, patch := range *patchList { |
| if strings.Contains(patch.Replacement, "package {") { |
| return nil |
| } |
| } |
| |
| // initial the fs |
| if fs == nil { |
| fs = pathtools.NewOsFs(os.Getenv("ANDROID_BUILD_TOP")) |
| } |
| |
| // initial the relativePath |
| if len(relativePath) == 0 { |
| relativePath = getModuleRelativePath() |
| } |
| // validate the relativePath |
| ok := hasFile(relativePath+"/Android.mk", fs) |
| // some modules in the existing test cases in the androidmk_test.go do not have a valid path |
| if !ok && len(relativePath) > 0 { |
| return fmt.Errorf("Cannot find an Android.mk file at path %q", relativePath) |
| } |
| |
| licenseKindsPropertyName := "android_license_kinds" |
| licenseFilesPropertyName := "android_license_files" |
| |
| androidBpFileErr := "// Error: No Android.bp file is found at path\n" + |
| "// %s\n" + |
| "// Please add one there with the needed license module first.\n" + |
| "// Then reset the default_applicable_licenses property below with the license module name.\n" |
| licenseModuleErr := "// Error: Cannot get the name of the license module in the\n" + |
| "// %s file.\n" + |
| "// If no such license module exists, please add one there first.\n" + |
| "// Then reset the default_applicable_licenses property below with the license module name.\n" |
| |
| defaultApplicableLicense := "Android-Apache-2.0" |
| var licenseModuleName, licensePatch string |
| var hasFileInParentDir bool |
| |
| // when LOCAL_NOTICE_FILE is not empty |
| if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) { |
| hasFileInParentDir = hasValueStartWithTwoDotsLiteralList(mod, licenseFilesPropertyName) |
| // if have LOCAL_NOTICE_FILE outside the current directory, need to find and refer to the license |
| // module in the LOCAL_NOTICE_FILE location directly and no new license module needs to be created |
| if hasFileInParentDir { |
| bpPath, ok := getPathFromProperty(mod, licenseFilesPropertyName, fs, relativePath) |
| if !ok { |
| bpDir, err := getDirFromProperty(mod, licenseFilesPropertyName, fs, relativePath) |
| if err != nil { |
| return err |
| } |
| licensePatch += fmt.Sprintf(androidBpFileErr, bpDir) |
| defaultApplicableLicense = "" |
| } else { |
| licenseModuleName, _ = getModuleName(bpPath, "license", fs) |
| if len(licenseModuleName) == 0 { |
| licensePatch += fmt.Sprintf(licenseModuleErr, bpPath) |
| } |
| defaultApplicableLicense = licenseModuleName |
| } |
| } else { |
| // if have LOCAL_NOTICE_FILE in the current directory, need to create a new license module |
| if len(relativePath) == 0 { |
| return fmt.Errorf("Cannot obtain the relative path of the Android.mk file") |
| } |
| licenseModuleName = strings.Replace(relativePath, "/", "_", -1) + "_license" |
| defaultApplicableLicense = licenseModuleName |
| } |
| } |
| |
| //add the package module |
| if hasNonEmptyLiteralListProperty(mod, licenseKindsPropertyName) { |
| licensePatch += "package {\n" + |
| " // See: http://go/android-license-faq\n" + |
| " default_applicable_licenses: [\n" + |
| " \"" + defaultApplicableLicense + "\",\n" + |
| " ],\n" + |
| "}\n" + |
| "\n" |
| } |
| |
| // append the license module when necessary |
| // when LOCAL_NOTICE_FILE is not empty and in the current directory, create a new license module |
| // otherwise, use the above default license directly |
| if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) && !hasFileInParentDir { |
| licenseKinds, err := mergeLiteralListPropertyValue(mod, licenseKindsPropertyName) |
| if err != nil { |
| return err |
| } |
| licenseFiles, err := mergeLiteralListPropertyValue(mod, licenseFilesPropertyName) |
| if err != nil { |
| return err |
| } |
| licensePatch += "license {\n" + |
| " name: \"" + licenseModuleName + "\",\n" + |
| " visibility: [\":__subpackages__\"],\n" + |
| " license_kinds: [\n" + |
| licenseKinds + |
| " ],\n" + |
| " license_text: [\n" + |
| licenseFiles + |
| " ],\n" + |
| "}\n" + |
| "\n" |
| } |
| |
| // add to the patchList |
| pos := mod.Pos().Offset |
| err := patchList.Add(pos, pos, licensePatch) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // merge the string vaules in a list property of a module into one string with expected format |
| func mergeLiteralListPropertyValue(mod *parser.Module, property string) (s string, err error) { |
| listValue, ok := getLiteralListPropertyValue(mod, property) |
| if !ok { |
| // if do not find |
| return "", fmt.Errorf("Cannot retrieve the %s.%s field", mod.Type, property) |
| } |
| for i := 0; i < len(listValue); i++ { |
| s += " \"" + listValue[i] + "\",\n" |
| } |
| return s, nil |
| } |
| |
| // check whether a string list property has any value starting with `../` |
| func hasValueStartWithTwoDotsLiteralList(mod *parser.Module, property string) bool { |
| listValue, ok := getLiteralListPropertyValue(mod, property) |
| if ok { |
| for i := 0; i < len(listValue); i++ { |
| if strings.HasPrefix(listValue[i], "../") { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| |
| // get the relative path from ANDROID_BUILD_TOP to the Android.mk file to be converted |
| func getModuleRelativePath() string { |
| // get the absolute path of the top of the tree |
| rootPath := os.Getenv("ANDROID_BUILD_TOP") |
| // get the absolute path of the `Android.mk` file to be converted |
| absPath := getModuleAbsolutePath() |
| // get the relative path of the `Android.mk` file to top of the tree |
| relModulePath, err := filepath.Rel(rootPath, absPath) |
| if err != nil { |
| return "" |
| } |
| return relModulePath |
| } |
| |
| // get the absolute path of the Android.mk file to be converted |
| func getModuleAbsolutePath() string { |
| // get the absolute path at where the `androidmk` commend is executed |
| curAbsPath, err := filepath.Abs(".") |
| if err != nil { |
| return "" |
| } |
| // the argument for the `androidmk` command could be |
| // 1. "./a/b/c/Android.mk"; 2. "a/b/c/Android.mk"; 3. "Android.mk" |
| argPath := flag.Arg(0) |
| if strings.HasPrefix(argPath, "./") { |
| argPath = strings.TrimPrefix(argPath, ".") |
| } |
| argPath = strings.TrimSuffix(argPath, "Android.mk") |
| if strings.HasSuffix(argPath, "/") { |
| argPath = strings.TrimSuffix(argPath, "/") |
| } |
| if len(argPath) > 0 && !strings.HasPrefix(argPath, "/") { |
| argPath = "/" + argPath |
| } |
| // get the absolute path of the `Android.mk` file to be converted |
| absPath := curAbsPath + argPath |
| return absPath |
| } |
| |
| // check whether a file exists in a filesystem |
| func hasFile(path string, fs pathtools.FileSystem) bool { |
| ok, _, _ := fs.Exists(path) |
| return ok |
| } |
| |
| // get the directory where an `Android.bp` file and the property files are expected to locate |
| func getDirFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, error) { |
| listValue, ok := getLiteralListPropertyValue(mod, property) |
| if !ok { |
| // if do not find |
| return "", fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property) |
| } |
| if len(listValue) == 0 { |
| // if empty |
| return "", fmt.Errorf("Cannot find the value of the %s.%s property", mod.Type, property) |
| } |
| if relativePath == "" { |
| relativePath = "." |
| } |
| _, isDir, _ := fs.Exists(relativePath) |
| if !isDir { |
| return "", fmt.Errorf("Cannot find the path %q", relativePath) |
| } |
| path := relativePath |
| for { |
| if !strings.HasPrefix(listValue[0], "../") { |
| break |
| } |
| path = filepath.Dir(path) |
| listValue[0] = strings.TrimPrefix(listValue[0], "../") |
| } |
| _, isDir, _ = fs.Exists(path) |
| if !isDir { |
| return "", fmt.Errorf("Cannot find the path %q", path) |
| } |
| return path, nil |
| } |
| |
| // get the path of the `Android.bp` file at the expected location where the property files locate |
| func getPathFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, bool) { |
| dir, err := getDirFromProperty(mod, property, fs, relativePath) |
| if err != nil { |
| return "", false |
| } |
| ok := hasFile(dir+"/Android.bp", fs) |
| if !ok { |
| return "", false |
| } |
| return dir + "/Android.bp", true |
| } |
| |
| // parse an Android.bp file to get the name of the first module with type of moduleType |
| func getModuleName(path string, moduleType string, fs pathtools.FileSystem) (string, error) { |
| tree, err := parserPath(path, fs) |
| if err != nil { |
| return "", err |
| } |
| for _, def := range tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok || mod.Type != moduleType { |
| continue |
| } |
| prop, ok := mod.GetProperty("name") |
| if !ok { |
| return "", fmt.Errorf("Cannot get the %s."+"name property", mod.Type) |
| } |
| propVal, ok := prop.Value.(*parser.String) |
| if ok { |
| return propVal.Value, nil |
| } |
| } |
| return "", fmt.Errorf("Cannot find the value of the %s."+"name property", moduleType) |
| } |
| |
| // parse an Android.bp file with the specific path |
| func parserPath(path string, fs pathtools.FileSystem) (tree *parser.File, err error) { |
| f, err := fs.Open(path) |
| if err != nil { |
| return tree, err |
| } |
| defer f.Close() |
| fileContent, _ := ioutil.ReadAll(f) |
| tree, err = parse(path, bytes.NewBufferString(string(fileContent))) |
| if err != nil { |
| return tree, err |
| } |
| return tree, nil |
| } |
| |
| // remove the incorrect property that Soong does not support |
| func removeIncorrectProperties(propName string) patchListModFunction { |
| return removeObsoleteProperty(propName) |
| } |
| |
| // the modules on the same Android.mk file are expected to have the same license |
| func haveSameLicense(f *Fixer) error { |
| androidLicenseProperties := []string{ |
| "android_license_kinds", |
| "android_license_conditions", |
| "android_license_files", |
| } |
| |
| var prevModuleName string |
| var prevLicenseKindsVals, prevLicenseConditionsVals, prevLicenseFilesVals []string |
| prevLicenseVals := [][]string{ |
| prevLicenseKindsVals, |
| prevLicenseConditionsVals, |
| prevLicenseFilesVals, |
| } |
| |
| for _, def := range f.tree.Defs { |
| mod, ok := def.(*parser.Module) |
| if !ok { |
| continue |
| } |
| for idx, property := range androidLicenseProperties { |
| curModuleName, ok := getLiteralStringPropertyValue(mod, "name") |
| // some modules in the existing test cases in the androidmk_test.go do not have name property |
| hasNameProperty := hasProperty(mod, "name") |
| if hasNameProperty && (!ok || len(curModuleName) == 0) { |
| return fmt.Errorf("Cannot retrieve the name property of a module of %s type.", mod.Type) |
| } |
| curVals, ok := getLiteralListPropertyValue(mod, property) |
| // some modules in the existing test cases in the androidmk_test.go do not have license-related property |
| hasLicenseProperty := hasProperty(mod, property) |
| if hasLicenseProperty && (!ok || len(curVals) == 0) { |
| // if do not find the property, or no value is found for the property |
| return fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property) |
| } |
| if len(prevLicenseVals[idx]) > 0 { |
| if !reflect.DeepEqual(prevLicenseVals[idx], curVals) { |
| return fmt.Errorf("Modules %s and %s are expected to have the same %s property.", |
| prevModuleName, curModuleName, property) |
| } |
| } |
| sort.Strings(curVals) |
| prevLicenseVals[idx] = curVals |
| prevModuleName = curModuleName |
| } |
| } |
| return nil |
| } |
| |
| func hasProperty(mod *parser.Module, propName string) bool { |
| _, ok := mod.GetProperty(propName) |
| return ok |
| } |