xref: /aosp_15_r20/build/make/tools/rbcrun/rbcrun/rbcrun.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker// Copyright 2021 Google LLC
2*9e94795aSAndroid Build Coastguard Worker//
3*9e94795aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*9e94795aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*9e94795aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*9e94795aSAndroid Build Coastguard Worker//
7*9e94795aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*9e94795aSAndroid Build Coastguard Worker//
9*9e94795aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*9e94795aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*9e94795aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9e94795aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*9e94795aSAndroid Build Coastguard Worker// limitations under the License.
14*9e94795aSAndroid Build Coastguard Worker
15*9e94795aSAndroid Build Coastguard Workerpackage main
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Workerimport (
18*9e94795aSAndroid Build Coastguard Worker	"flag"
19*9e94795aSAndroid Build Coastguard Worker	"fmt"
20*9e94795aSAndroid Build Coastguard Worker	"os"
21*9e94795aSAndroid Build Coastguard Worker	"rbcrun"
22*9e94795aSAndroid Build Coastguard Worker	"regexp"
23*9e94795aSAndroid Build Coastguard Worker	"strings"
24*9e94795aSAndroid Build Coastguard Worker
25*9e94795aSAndroid Build Coastguard Worker	"go.starlark.net/starlark"
26*9e94795aSAndroid Build Coastguard Worker)
27*9e94795aSAndroid Build Coastguard Worker
28*9e94795aSAndroid Build Coastguard Workervar (
29*9e94795aSAndroid Build Coastguard Worker	allowExternalEntrypoint = flag.Bool("allow_external_entrypoint", false, "allow the entrypoint starlark file to be outside of the source tree")
30*9e94795aSAndroid Build Coastguard Worker	modeFlag  = flag.String("mode", "", "the general behavior of rbcrun. Can be \"rbc\" or \"make\". Required.")
31*9e94795aSAndroid Build Coastguard Worker	rootdir  = flag.String("d", ".", "the value of // for load paths")
32*9e94795aSAndroid Build Coastguard Worker	perfFile = flag.String("perf", "", "save performance data")
33*9e94795aSAndroid Build Coastguard Worker	identifierRe = regexp.MustCompile("[a-zA-Z_][a-zA-Z0-9_]*")
34*9e94795aSAndroid Build Coastguard Worker)
35*9e94795aSAndroid Build Coastguard Worker
36*9e94795aSAndroid Build Coastguard Workerfunc getEntrypointStarlarkFile() string {
37*9e94795aSAndroid Build Coastguard Worker	filename := ""
38*9e94795aSAndroid Build Coastguard Worker
39*9e94795aSAndroid Build Coastguard Worker	for _, arg := range flag.Args() {
40*9e94795aSAndroid Build Coastguard Worker		if filename == "" {
41*9e94795aSAndroid Build Coastguard Worker			filename = arg
42*9e94795aSAndroid Build Coastguard Worker		} else {
43*9e94795aSAndroid Build Coastguard Worker			quit("only one file can be executed\n")
44*9e94795aSAndroid Build Coastguard Worker		}
45*9e94795aSAndroid Build Coastguard Worker	}
46*9e94795aSAndroid Build Coastguard Worker	if filename == "" {
47*9e94795aSAndroid Build Coastguard Worker		flag.Usage()
48*9e94795aSAndroid Build Coastguard Worker		os.Exit(1)
49*9e94795aSAndroid Build Coastguard Worker	}
50*9e94795aSAndroid Build Coastguard Worker	return filename
51*9e94795aSAndroid Build Coastguard Worker}
52*9e94795aSAndroid Build Coastguard Worker
53*9e94795aSAndroid Build Coastguard Workerfunc getMode() rbcrun.ExecutionMode {
54*9e94795aSAndroid Build Coastguard Worker	switch *modeFlag {
55*9e94795aSAndroid Build Coastguard Worker	case "rbc":
56*9e94795aSAndroid Build Coastguard Worker		return rbcrun.ExecutionModeRbc
57*9e94795aSAndroid Build Coastguard Worker	case "make":
58*9e94795aSAndroid Build Coastguard Worker		return rbcrun.ExecutionModeScl
59*9e94795aSAndroid Build Coastguard Worker	case "":
60*9e94795aSAndroid Build Coastguard Worker		quit("-mode flag is required.")
61*9e94795aSAndroid Build Coastguard Worker	default:
62*9e94795aSAndroid Build Coastguard Worker		quit("Unknown -mode value %q, expected 1 of \"rbc\", \"make\"", *modeFlag)
63*9e94795aSAndroid Build Coastguard Worker	}
64*9e94795aSAndroid Build Coastguard Worker	return rbcrun.ExecutionModeScl
65*9e94795aSAndroid Build Coastguard Worker}
66*9e94795aSAndroid Build Coastguard Worker
67*9e94795aSAndroid Build Coastguard Workervar makeStringReplacer = strings.NewReplacer("#", "\\#", "$", "$$")
68*9e94795aSAndroid Build Coastguard Worker
69*9e94795aSAndroid Build Coastguard Workerfunc cleanStringForMake(s string) (string, error) {
70*9e94795aSAndroid Build Coastguard Worker	if strings.ContainsAny(s, "\\\n") {
71*9e94795aSAndroid Build Coastguard Worker		// \\ in make is literally \\, not a single \, so we can't allow them.
72*9e94795aSAndroid Build Coastguard Worker		// \<newline> in make will produce a space, not a newline.
73*9e94795aSAndroid Build Coastguard Worker		return "", fmt.Errorf("starlark strings exported to make cannot contain backslashes or newlines")
74*9e94795aSAndroid Build Coastguard Worker	}
75*9e94795aSAndroid Build Coastguard Worker	return makeStringReplacer.Replace(s), nil
76*9e94795aSAndroid Build Coastguard Worker}
77*9e94795aSAndroid Build Coastguard Worker
78*9e94795aSAndroid Build Coastguard Workerfunc getValueInMakeFormat(value starlark.Value, allowLists bool) (string, error) {
79*9e94795aSAndroid Build Coastguard Worker	switch v := value.(type) {
80*9e94795aSAndroid Build Coastguard Worker	case starlark.String:
81*9e94795aSAndroid Build Coastguard Worker		if cleanedValue, err := cleanStringForMake(v.GoString()); err == nil {
82*9e94795aSAndroid Build Coastguard Worker			return cleanedValue, nil
83*9e94795aSAndroid Build Coastguard Worker		} else {
84*9e94795aSAndroid Build Coastguard Worker			return "", err
85*9e94795aSAndroid Build Coastguard Worker		}
86*9e94795aSAndroid Build Coastguard Worker	case starlark.Int:
87*9e94795aSAndroid Build Coastguard Worker		return v.String(), nil
88*9e94795aSAndroid Build Coastguard Worker	case *starlark.List:
89*9e94795aSAndroid Build Coastguard Worker		if !allowLists {
90*9e94795aSAndroid Build Coastguard Worker			return "", fmt.Errorf("nested lists are not allowed to be exported from starlark to make, flatten the list in starlark first")
91*9e94795aSAndroid Build Coastguard Worker		}
92*9e94795aSAndroid Build Coastguard Worker		result := ""
93*9e94795aSAndroid Build Coastguard Worker		for i := 0; i < v.Len(); i++ {
94*9e94795aSAndroid Build Coastguard Worker			value, err := getValueInMakeFormat(v.Index(i), false)
95*9e94795aSAndroid Build Coastguard Worker			if err != nil {
96*9e94795aSAndroid Build Coastguard Worker				return "", err
97*9e94795aSAndroid Build Coastguard Worker			}
98*9e94795aSAndroid Build Coastguard Worker			if i > 0 {
99*9e94795aSAndroid Build Coastguard Worker				result += " "
100*9e94795aSAndroid Build Coastguard Worker			}
101*9e94795aSAndroid Build Coastguard Worker			result += value
102*9e94795aSAndroid Build Coastguard Worker		}
103*9e94795aSAndroid Build Coastguard Worker		return result, nil
104*9e94795aSAndroid Build Coastguard Worker	default:
105*9e94795aSAndroid Build Coastguard Worker		return "", fmt.Errorf("only starlark strings, ints, and lists of strings/ints can be exported to make. Please convert all other types in starlark first. Found type: %s", value.Type())
106*9e94795aSAndroid Build Coastguard Worker	}
107*9e94795aSAndroid Build Coastguard Worker}
108*9e94795aSAndroid Build Coastguard Worker
109*9e94795aSAndroid Build Coastguard Workerfunc printVarsInMakeFormat(globals starlark.StringDict) error {
110*9e94795aSAndroid Build Coastguard Worker	// We could just directly export top level variables by name instead of going through
111*9e94795aSAndroid Build Coastguard Worker	// a variables_to_export_to_make dictionary, but that wouldn't allow for exporting a
112*9e94795aSAndroid Build Coastguard Worker	// runtime-defined number of variables to make. This can be important because dictionaries
113*9e94795aSAndroid Build Coastguard Worker	// in make are often represented by a unique variable for every key in the dictionary.
114*9e94795aSAndroid Build Coastguard Worker	variablesValue, ok := globals["variables_to_export_to_make"]
115*9e94795aSAndroid Build Coastguard Worker	if !ok {
116*9e94795aSAndroid Build Coastguard Worker		return fmt.Errorf("expected top-level starlark file to have a \"variables_to_export_to_make\" variable")
117*9e94795aSAndroid Build Coastguard Worker	}
118*9e94795aSAndroid Build Coastguard Worker	variables, ok := variablesValue.(*starlark.Dict)
119*9e94795aSAndroid Build Coastguard Worker	if !ok {
120*9e94795aSAndroid Build Coastguard Worker		return fmt.Errorf("expected variables_to_export_to_make to be a dict, got %s", variablesValue.Type())
121*9e94795aSAndroid Build Coastguard Worker	}
122*9e94795aSAndroid Build Coastguard Worker
123*9e94795aSAndroid Build Coastguard Worker	for _, varTuple := range variables.Items() {
124*9e94795aSAndroid Build Coastguard Worker		varNameStarlark, ok := varTuple.Index(0).(starlark.String)
125*9e94795aSAndroid Build Coastguard Worker		if !ok {
126*9e94795aSAndroid Build Coastguard Worker			return fmt.Errorf("all keys in variables_to_export_to_make must be strings, but got %q", varTuple.Index(0).Type())
127*9e94795aSAndroid Build Coastguard Worker		}
128*9e94795aSAndroid Build Coastguard Worker		varName := varNameStarlark.GoString()
129*9e94795aSAndroid Build Coastguard Worker		if !identifierRe.MatchString(varName) {
130*9e94795aSAndroid Build Coastguard Worker			return fmt.Errorf("all variables at the top level starlark file must be valid c identifiers, but got %q", varName)
131*9e94795aSAndroid Build Coastguard Worker		}
132*9e94795aSAndroid Build Coastguard Worker		if varName == "LOADED_STARLARK_FILES" {
133*9e94795aSAndroid Build Coastguard Worker			return fmt.Errorf("the name LOADED_STARLARK_FILES is reserved for use by the starlark interpreter")
134*9e94795aSAndroid Build Coastguard Worker		}
135*9e94795aSAndroid Build Coastguard Worker		valueMake, err := getValueInMakeFormat(varTuple.Index(1), true)
136*9e94795aSAndroid Build Coastguard Worker		if err != nil {
137*9e94795aSAndroid Build Coastguard Worker			return err
138*9e94795aSAndroid Build Coastguard Worker		}
139*9e94795aSAndroid Build Coastguard Worker		// The :=$= is special Kati syntax that means "set and make readonly"
140*9e94795aSAndroid Build Coastguard Worker		fmt.Printf("%s :=$= %s\n", varName, valueMake)
141*9e94795aSAndroid Build Coastguard Worker	}
142*9e94795aSAndroid Build Coastguard Worker	return nil
143*9e94795aSAndroid Build Coastguard Worker}
144*9e94795aSAndroid Build Coastguard Worker
145*9e94795aSAndroid Build Coastguard Workerfunc main() {
146*9e94795aSAndroid Build Coastguard Worker	flag.Parse()
147*9e94795aSAndroid Build Coastguard Worker	filename := getEntrypointStarlarkFile()
148*9e94795aSAndroid Build Coastguard Worker	mode := getMode()
149*9e94795aSAndroid Build Coastguard Worker
150*9e94795aSAndroid Build Coastguard Worker	if os.Chdir(*rootdir) != nil {
151*9e94795aSAndroid Build Coastguard Worker		quit("could not chdir to %s\n", *rootdir)
152*9e94795aSAndroid Build Coastguard Worker	}
153*9e94795aSAndroid Build Coastguard Worker	if *perfFile != "" {
154*9e94795aSAndroid Build Coastguard Worker		pprof, err := os.Create(*perfFile)
155*9e94795aSAndroid Build Coastguard Worker		if err != nil {
156*9e94795aSAndroid Build Coastguard Worker			quit("%s: err", *perfFile)
157*9e94795aSAndroid Build Coastguard Worker		}
158*9e94795aSAndroid Build Coastguard Worker		defer pprof.Close()
159*9e94795aSAndroid Build Coastguard Worker		if err := starlark.StartProfile(pprof); err != nil {
160*9e94795aSAndroid Build Coastguard Worker			quit("%s\n", err)
161*9e94795aSAndroid Build Coastguard Worker		}
162*9e94795aSAndroid Build Coastguard Worker	}
163*9e94795aSAndroid Build Coastguard Worker	variables, loadedStarlarkFiles, err := rbcrun.Run(filename, nil, mode, *allowExternalEntrypoint)
164*9e94795aSAndroid Build Coastguard Worker	rc := 0
165*9e94795aSAndroid Build Coastguard Worker	if *perfFile != "" {
166*9e94795aSAndroid Build Coastguard Worker		if err2 := starlark.StopProfile(); err2 != nil {
167*9e94795aSAndroid Build Coastguard Worker			fmt.Fprintln(os.Stderr, err2)
168*9e94795aSAndroid Build Coastguard Worker			rc = 1
169*9e94795aSAndroid Build Coastguard Worker		}
170*9e94795aSAndroid Build Coastguard Worker	}
171*9e94795aSAndroid Build Coastguard Worker	if err != nil {
172*9e94795aSAndroid Build Coastguard Worker		if evalErr, ok := err.(*starlark.EvalError); ok {
173*9e94795aSAndroid Build Coastguard Worker			quit("%s\n", evalErr.Backtrace())
174*9e94795aSAndroid Build Coastguard Worker		} else {
175*9e94795aSAndroid Build Coastguard Worker			quit("%s\n", err)
176*9e94795aSAndroid Build Coastguard Worker		}
177*9e94795aSAndroid Build Coastguard Worker	}
178*9e94795aSAndroid Build Coastguard Worker	if mode == rbcrun.ExecutionModeScl {
179*9e94795aSAndroid Build Coastguard Worker		if err := printVarsInMakeFormat(variables); err != nil {
180*9e94795aSAndroid Build Coastguard Worker			quit("%s\n", err)
181*9e94795aSAndroid Build Coastguard Worker		}
182*9e94795aSAndroid Build Coastguard Worker		fmt.Printf("LOADED_STARLARK_FILES := %s\n", strings.Join(loadedStarlarkFiles, " "))
183*9e94795aSAndroid Build Coastguard Worker	}
184*9e94795aSAndroid Build Coastguard Worker	os.Exit(rc)
185*9e94795aSAndroid Build Coastguard Worker}
186*9e94795aSAndroid Build Coastguard Worker
187*9e94795aSAndroid Build Coastguard Workerfunc quit(format string, s ...interface{}) {
188*9e94795aSAndroid Build Coastguard Worker	fmt.Fprintf(os.Stderr, format, s...)
189*9e94795aSAndroid Build Coastguard Worker	os.Exit(2)
190*9e94795aSAndroid Build Coastguard Worker}
191