| // 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 |
| } |