xref: /aosp_15_r20/external/skia/bazel/exporter/cmake_workspace.go (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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