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