1// Copyright 2022 Google LLC 2// 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5 6package exporter 7 8import ( 9 "fmt" 10 "io" 11 "sort" 12 13 "go.skia.org/infra/go/skerr" 14 "go.skia.org/infra/go/util" 15 "go.skia.org/skia/bazel/exporter/build_proto/build" 16) 17 18// cmakeWorkspace represents the entire state of a CMake project. 19type cmakeWorkspace struct { 20 // Map Bazel rule names to cmakeRule instances. Holding pointer 21 // values as the rules are mutable. 22 rules map[string]*cmakeRule 23} 24 25// writeState tracks the state of an in-progress write of the workspace. 26type writeState struct { 27 writtenRules []string // All cmakeRule's written during write. 28} 29 30// newCMakeWorkspace will create a new CMake workspace object. 31func newCMakeWorkspace() *cmakeWorkspace { 32 return &cmakeWorkspace{rules: map[string]*cmakeRule{}} 33} 34 35// Determine if a rule has been written. 36func (s *writeState) isRuleWritten(ruleName string) bool { 37 return util.In(ruleName, s.writtenRules) 38} 39 40// setRuleWritten will mark the rule as having been written. 41func (s *writeState) setRuleWritten(ruleName string) { 42 s.writtenRules = append(s.writtenRules, ruleName) 43} 44 45// getRule will return the CMake wrapper for a Bazel rule given the 46// rule name. Will return nil if there is no corresponding rule. 47func (w *cmakeWorkspace) getRule(name string) *cmakeRule { 48 return w.rules[name] 49} 50 51// createRule will create (if necessary) a new CMake rule object 52// for the given rule name. 53func (w *cmakeWorkspace) createRule(rule *build.Rule) *cmakeRule { 54 if r := w.getRule(rule.GetName()); r != nil { 55 return r 56 } 57 r := newCMakeRule(rule) 58 w.rules[rule.GetName()] = r 59 return r 60} 61 62// writeRule will write the given rule to the writer. 63// It will first write all dependent rules so that they appear 64// in the CMake project file before the rule that depends on them. 65func (w *cmakeWorkspace) writeRule(writer io.Writer, r *cmakeRule, state *writeState) (int, error) { 66 nb := 0 67 if !r.hasSrcs() { 68 return 0, nil 69 } 70 // First write all dependencies because CMake does not support forward references. 71 for _, name := range r.deps { 72 dep := w.getRule(name) 73 if dep == nil { 74 return 0, skerr.Fmt(`cannot find rule %q`, name) 75 } 76 n, err := w.writeRule(writer, dep, state) 77 if err != nil { 78 return nb, skerr.Wrap(err) 79 } 80 nb += n 81 } 82 if state.isRuleWritten(r.getName()) { 83 return nb, nil 84 } 85 num, err := fmt.Fprintln(writer) 86 if err != nil { 87 return nb, skerr.Wrap(err) 88 } 89 nb += num 90 num, err = r.write(writer) 91 if err != nil { 92 return nb, skerr.Wrap(err) 93 } 94 state.setRuleWritten(r.getName()) 95 nb += num 96 return nb, nil 97} 98 99// Write this workspace using the given writer. 100func (w *cmakeWorkspace) write(writer io.Writer) (int, error) { 101 // Sort rule names to ensure a deterministic output. 102 var sortedRuleNames []string 103 for name := range w.rules { 104 sortedRuleNames = append(sortedRuleNames, name) 105 } 106 sort.Strings(sortedRuleNames) 107 108 var state writeState 109 nb := 0 110 for _, name := range sortedRuleNames { 111 r := w.rules[name] 112 num, err := w.writeRule(writer, r, &state) 113 if err != nil { 114 return nb, skerr.Wrap(err) 115 } 116 nb += num 117 } 118 return nb, nil 119} 120