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