blob: fe9c20b8e77d35997386627d2b8aeca6aeaf8f89 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package codifier
import (
"log"
"strings"
)
// In Codifier, a "scope" is a section of a GN build file. It's defined flexibly
// so that any string of the form `a("b"){c}` or `a("b")[c]` can be extracted as
// a scope of type a, name b, and containing c. The use of scopes allows
// Codifier to narrow its field of operation, increasing safety and reducing
// errors.
//
// For example, in a file containing "a(b){c} a(bb){c}", the `p.Scope("a", "b")`
// operator would ensure that subsequent operators chained to p would only
// operate on the `a("b"){c}` section of the file and not the `a("bb"){c}`
// section of the same type, a.
//
// Similarly, `p.Scope("a", "b")` applied to `d("bb"){ a("b"){c} }` would narrow
// subsequent operation to just the inner "b" section, ensuring no modifications
// to the outer "bb" section that contains it. Additionally, scopes can be
// extracted by name, or by type, allowing operations on sections of a given
// name or type only.
//
// Finally, it's possible to apply operators to multiple sections of the same
// type.
//
// When operators use scopes to narrow their changes, the applyChange operator
// must also be called to propagate the changes to the parent scope. If there
// are a series of narrowing scope operators, all of their changes can be
// propagated using a single ApplyChanges() operator.
//
// For example, to only change the deps of test "b" in group "a":
//
// group("a") {
// test("b") {
// deps = ["foo"]
// }
// }
//
// group("c") {
// test("d") {
// deps = ["bar"]
// }
// }
//
// ...we could use
// p = p.ScopeNamed("a").ScopeNamed("b").<other operators>.ApplyChanges().
//
// Scope returns the scope with the given type and name. Here, there can only be
// one of them. Because it sets a new Scope, it parents the caller and returns
// the child. Change() will need to be called on the child. The type/kind is
// used as a key to store the name. The name is used as the key to store the
// type.
func (p *Proc) Scope(kind, name string) *Proc {
if p == nil {
log.Println(" Scope() error: nil receiver")
return nil
}
scopes, err := sectionsWithTypeAndName(p.replacement, kind, name, '{', '}')
return p.addChildScope(scopes, err, "Scope()", kind, name)
}
// ScopeNamed returns the scope with the given name. There can only be one of
// them. Because it sets a new scope, it parents the caller and returns the
// child. Change() will need to be called on the child. The type/kind is used as a
// key to store the name. The name is used as the key to store the type.
func (p *Proc) ScopeNamed(name string) *Proc {
if p == nil {
log.Println(" ScopeNamed() error: nil receiver")
return nil
}
scopes, err := sectionsNamed(p.replacement, name, '{', '}')
return p.addChildScope(scopes, err, "ScopeNamed()", "--", name)
}
// scopeOfType returns the scope with the given type. There can only be
// one of them. If there are expected to be more, use ForEachScopeOfType().
// Because it sets a new scope, it parents the caller and returns the child.
// Change() will need to be called on the child. The type/kind is used as a key
// to store the name. The name is used as the key to store the type.
func (p *Proc) scopeOfType(kind string) *Proc {
if p == nil {
log.Println(" scopeOfType() error: nil receiver")
return nil
}
scopes, err := sectionsOfType(p.replacement, kind, '{', '}')
return p.addChildScope(scopes, err, "scopeOfType()", kind, "--")
}
// Add a child scope, if valid. Check the error and then ensure that there is
// only one scope provided. Use arguments fn, kind, and name as informational
// for logging; the data stored in the child scope is taken from the given
// scope.
func (p *Proc) addChildScope(scopes []SectionType, err error, fn, kind, name string) *Proc {
if p == nil {
log.Println(" addChildScope() error: nil receiver")
return nil
}
if err != nil {
log.Printf(" %s error: %v", fn, err)
return nil
}
if scopes == nil {
log.Printf(" %s: couldn't find scope of type %q named %q", fn, kind, name)
return nil
}
if len(scopes) > 1 {
log.Printf(" %s: found more than one scope of type %q named %q", fn, kind, name)
log.Printf(" %s: len() = %d", fn, len(scopes))
log.Printf(" %s: first = %q", fn, scopes[0].Contents)
log.Printf(" %s: second = %q", fn, scopes[1].Contents)
return nil
}
q := NewProcFromString(scopes[0].Contents)
q.parent = p
q.Store[scopes[0].Kind] = scopes[0].Name // Under the type, store the name.
q.Store[scopes[0].Name] = scopes[0].Kind // Under the name, store the type.
return q
}
// ForEachScopeOfType extracts all of the scopes with the given type. It then
// creates a child with the extracted scope and applies the given function to
// the child. The function is defined on the child proc and its index number.
// Then any chains of changes are applied. Finally, if there have been any
// changes, the replacement is made in p and the process continues.
func (p *Proc) ForEachScopeOfType(t string, fn func(i int, c *Proc) *Proc) *Proc {
if p == nil {
log.Println(" ForEachScopeOfType() error: nil receiver")
return nil
}
if t == "" {
log.Println(" ForEachScopeOfType() error: blank type")
return nil
}
scopes, err := sectionsOfType(p.replacement, t, '{', '}')
if err != nil {
log.Printf(" ForEachScopeOfType() error: %v", err)
return nil
}
for i, scope := range scopes {
c := NewProcFromString(scope.Contents)
c.Store[scope.Name] = scope.Kind
c.Store[scope.Kind] = scope.Name
c = fn(i, c)
if c != nil {
c = c.ApplyChanges()
}
if c.replacement != scope.Contents {
// Note that this is a replace-all. It's possible that the contents occur
// in more than one place in p's replacement string, leading to unexpected
// results. TODO(gboone): Determine the general-case solution and fix.
p.replacement = strings.Replace(p.replacement, scope.Contents, c.replacement, -1)
}
}
return p
}
// applyChange applies the changes of the child to the parent. It returns the parent. If there is no
// parent, then the current Proc is returned.
func (p *Proc) applyChange() *Proc {
if p == nil {
log.Println(" applyChange() error: nil receiver")
return nil
}
if p.parent == nil {
return p
}
if p.replacement != p.original {
p.parent.replacement = strings.Replace(p.parent.replacement, p.original, p.replacement, -1)
p.parent.changedFiles.Add(p.changedFiles...)
// Copying up the store allows data to be passed back to the parent and
// allows the appearance of a single store along a chain of operators. But
// it can cause collisions. For example, a sequence like
// p.ScopeNamed("a").ExtractList("deps").ApplyChanges().
// ScopeNamed("b").ExtractList("deps").ApplyChanges()
// would fail on the second ApplyChanges() because p would already have a
// "deps" entry from the copyStore in the first ApplyChanges(). Use
// ClearStore() to clear the store prior to collisions.
p.parent.copyStore(p)
}
return p.parent
}
// ApplyChanges traverses up the chain of parents, applying all of the child changes.
func (p *Proc) ApplyChanges() *Proc {
if p == nil {
log.Println(" ApplyChanges() error: nil receiver")
return nil
}
// TODO(gboone@): Add a warning or error if q.child != nil.
// Do for all parents.
for p.parent != nil {
p = p.applyChange()
}
return p
}