diff options
Diffstat (limited to 'bpfix/bpfix/bpfix.go')
-rw-r--r-- | bpfix/bpfix/bpfix.go | 263 |
1 files changed, 241 insertions, 22 deletions
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go index 84454907..e4d4e34e 100644 --- a/bpfix/bpfix/bpfix.go +++ b/bpfix/bpfix/bpfix.go @@ -18,17 +18,36 @@ package bpfix import ( "bytes" + "errors" "fmt" + "io" "path/filepath" "github.com/google/blueprint/parser" ) +// 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 { - simplifyKnownRedundantVariables bool - rewriteIncorrectAndroidmkPrebuilts bool + simplifyKnownRedundantVariables bool + rewriteIncorrectAndroidmkPrebuilts bool + rewriteIncorrectAndroidmkAndroidLibraries bool + mergeMatchingModuleProperties bool } func NewFixRequest() FixRequest { @@ -39,24 +58,39 @@ func (r FixRequest) AddAll() (result FixRequest) { result = r result.simplifyKnownRedundantVariables = true result.rewriteIncorrectAndroidmkPrebuilts = true + result.rewriteIncorrectAndroidmkAndroidLibraries = true + result.mergeMatchingModuleProperties = true return result } -// FixTree repeatedly applies the fixes listed in the given FixRequest to the given File +type Fixer struct { + tree *parser.File +} + +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 FixTree(tree *parser.File, config FixRequest) error { - prevIdentifier, err := fingerprint(tree) +func (f *Fixer) Fix(config FixRequest) (*parser.File, error) { + prevIdentifier, err := f.fingerprint() if err != nil { - return err + return nil, err } maxNumIterations := 20 i := 0 for { - err = fixTreeOnce(tree, config) - newIdentifier, err := fingerprint(tree) + err = f.fixTreeOnce(config) + newIdentifier, err := f.fingerprint() if err != nil { - return err + return nil, err } if bytes.Equal(newIdentifier, prevIdentifier) { break @@ -67,31 +101,70 @@ func FixTree(tree *parser.File, config FixRequest) error { // detect infinite loop i++ if i >= maxNumIterations { - return fmt.Errorf("Applied fixes %s times and yet the tree continued to change. Is there an infinite loop?", i) + return nil, fmt.Errorf("Applied fixes %d times and yet the tree continued to change. Is there an infinite loop?", i) break } } - return err + return f.tree, err } // returns a unique identifier for the given tree that can be used to determine whether the tree changed -func fingerprint(tree *parser.File) (fingerprint []byte, err error) { - bytes, err := parser.Print(tree) +func (f *Fixer) fingerprint() (fingerprint []byte, err error) { + bytes, err := parser.Print(f.tree) if err != nil { return nil, err } return bytes, nil } -func fixTreeOnce(tree *parser.File, config FixRequest) error { +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 { if config.simplifyKnownRedundantVariables { - err := simplifyKnownPropertiesDuplicatingEachOther(tree) + err := f.simplifyKnownPropertiesDuplicatingEachOther() if err != nil { return err } } if config.rewriteIncorrectAndroidmkPrebuilts { - err := rewriteIncorrectAndroidmkPrebuilts(tree) + err := f.rewriteIncorrectAndroidmkPrebuilts() + if err != nil { + return err + } + } + + if config.rewriteIncorrectAndroidmkAndroidLibraries { + err := f.rewriteIncorrectAndroidmkAndroidLibraries() + if err != nil { + return err + } + } + + if config.mergeMatchingModuleProperties { + err := f.mergeMatchingModuleProperties() if err != nil { return err } @@ -99,13 +172,13 @@ func fixTreeOnce(tree *parser.File, config FixRequest) error { return nil } -func simplifyKnownPropertiesDuplicatingEachOther(tree *parser.File) error { +func (f *Fixer) simplifyKnownPropertiesDuplicatingEachOther() error { // remove from local_include_dirs anything in export_include_dirs - return removeMatchingModuleListProperties(tree, "export_include_dirs", "local_include_dirs") + return f.removeMatchingModuleListProperties("export_include_dirs", "local_include_dirs") } -func rewriteIncorrectAndroidmkPrebuilts(tree *parser.File) error { - for _, def := range tree.Defs { +func (f *Fixer) rewriteIncorrectAndroidmkPrebuilts() error { + for _, def := range f.tree.Defs { mod, ok := def.(*parser.Module) if !ok { continue @@ -127,6 +200,7 @@ func rewriteIncorrectAndroidmkPrebuilts(tree *parser.File) error { switch filepath.Ext(src.Value) { case ".jar": renameProperty(mod, "srcs", "jars") + case ".aar": renameProperty(mod, "srcs", "aars") mod.Type = "android_library_import" @@ -139,6 +213,146 @@ func rewriteIncorrectAndroidmkPrebuilts(tree *parser.File) error { return nil } +func (f *Fixer) rewriteIncorrectAndroidmkAndroidLibraries() error { + for _, def := range f.tree.Defs { + mod, ok := def.(*parser.Module) + if !ok { + 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 = "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 +} + +func (f *Fixer) mergeMatchingModuleProperties() 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 := mergeMatchingProperties(&mod.Properties, buf, &patchlist) + if err != nil { + return err + } + } + + newBuf := new(bytes.Buffer) + err = patchlist.Apply(bytes.NewReader(buf), newBuf) + if err != nil { + return err + } + + newTree, err := parse(f.tree.Name, newBuf) + if err != nil { + return err + } + + f.tree = newTree + + return nil +} + +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 { + 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(items *parser.List, removals *parser.List) { writeIndex := 0 @@ -163,8 +377,8 @@ func filterExpressionList(items *parser.List, removals *parser.List) { } // Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k] -func removeMatchingModuleListProperties(tree *parser.File, canonicalName string, legacyName string) error { - for _, def := range tree.Defs { +func (f *Fixer) removeMatchingModuleListProperties(canonicalName string, legacyName string) error { + for _, def := range f.tree.Defs { mod, ok := def.(*parser.Module) if !ok { continue @@ -182,6 +396,11 @@ func removeMatchingModuleListProperties(tree *parser.File, canonicalName string, return nil } +func hasNonEmptyLiteralListProperty(mod *parser.Module, name string) bool { + list, found := getLiteralListProperty(mod, name) + return found && len(list.Values) > 0 +} + func getLiteralListProperty(mod *parser.Module, name string) (list *parser.List, found bool) { prop, ok := mod.GetProperty(name) if !ok { |