blob: 473f8baa5f840c5a158df3ec07b48a42b1d6a1b3 [file] [log] [blame]
/*
* Copyright 2019 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Processes CMake input files to handle CMake-style substitutions.
//
// For details see:
// https://cmake.org/cmake/help/latest/command/configure_file.html
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"
)
type cmakeValue string
type cmakeDefines map[string]cmakeValue
var (
atOnly = flag.Bool("at_only", false, "Restrict variable replacement to references of the form @VAR@.")
strict = flag.Bool("strict", false, "Exit with an error if an undefined variable is inspected.")
verbose = flag.Bool("verbose", false, "Print an error if an undefined variable is inspected.")
jsonValues = flag.String("json", "", "Parse the string as a JSON dictionary to use as CMake defines.")
outFile = flag.String("outfile", "-", "File to which output should be written. Defaults to stdout.")
atPattern = regexp.MustCompile("@([^@]*)@")
allPattern = regexp.MustCompile(`\${([^}]*)}|` + atPattern.String())
defPattern = regexp.MustCompile(`^#(\s*)cmakedefine(01)?\s+([^\s]+)`)
falsePattern = regexp.MustCompile("^(0|OFF|NO|FALSE|N|IGNORE|(.*-)?NOTFOUND)$")
varMap = &cmakeDefines{}
)
func (cv *cmakeValue) UnmarshalJSON(data []byte) error {
var i interface{}
json.Unmarshal(data, &i)
switch v := i.(type) {
case bool:
*cv = cmakeValue(strings.ToUpper(fmt.Sprint(v)))
case float64:
*cv = cmakeValue(fmt.Sprint(v))
case string:
*cv = cmakeValue(v)
case nil:
*cv = ""
default:
return fmt.Errorf("expected bool, string or number, found: %T", v)
}
return nil
}
// Get returns the currently defined value or the empty string.
func (vars *cmakeDefines) Get(key string) string {
if value, ok := (*vars)[key]; ok {
return string(value)
} else if *strict {
log.Fatalln("Undefined variable: ", key)
} else if *verbose {
log.Println("Undefined variable: ", key)
}
return ""
}
// Defined returns true if the value is set to a value "not considered false".
// See https://cmake.org/cmake/help/latest/command/if.html#command:if for more details.
func (vars *cmakeDefines) Defined(key string) bool {
value, ok := (*vars)[key]
if !ok {
if *strict {
log.Fatalln("Undefined variable: ", key)
} else if *verbose {
log.Println("Undefined variable: ", key)
}
}
return ok && len(value) > 0 && !falsePattern.MatchString(string(value))
}
func (vars *cmakeDefines) String() string {
bytes, err := json.Marshal(vars)
if err != nil {
panic(err)
}
return string(bytes)
}
func init() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <input>\n", os.Args[0])
flag.PrintDefaults()
}
}
func transform(input, output *os.File) error {
scanner := bufio.NewScanner(input)
for scanner.Scan() {
text := scanner.Bytes()
text = replaceLine(text)
text = append(text, '\n')
if _, err := output.Write(text); err != nil {
return err
}
}
return scanner.Err()
}
func replaceLine(text []byte) []byte {
// There should only be a single match.
submatches := defPattern.FindSubmatch(text)
if submatches == nil {
return replaceVars(text)
}
spacing := submatches[1]
isBinary := len(submatches[2]) == 2
varName := submatches[3]
var result bytes.Buffer
if isBinary {
result.WriteByte('#')
result.Write(spacing)
result.WriteString("define ")
result.Write(varName)
result.WriteByte(' ')
if varMap.Defined(string(varName)) {
result.WriteByte('1')
} else {
result.WriteByte('0')
}
} else if varMap.Defined(string(varName)) {
result.WriteByte('#')
result.Write(spacing)
result.WriteString("define ")
result.Write(varName)
result.Write(replaceVars(text[len(submatches[0]):]))
} else {
result.WriteString("/* #undef ")
result.Write(varName)
result.WriteString(" */")
}
return result.Bytes()
}
func replaceVars(text []byte) []byte {
pattern := allPattern
if *atOnly {
pattern = atPattern
}
return pattern.ReplaceAllFunc(text, func(key []byte) []byte {
// Chop off the delimiters before looking up the variable.
if key[0] == '@' {
key = key[1 : len(key)-1]
} else {
key = key[2 : len(key)-1]
}
return []byte(varMap.Get(string(key)))
})
}
func main() {
flag.Parse()
if flag.NArg() != 1 {
fmt.Fprintf(flag.CommandLine.Output(), "Missing required input file\n")
flag.Usage()
os.Exit(1)
}
if err := json.Unmarshal([]byte(*jsonValues), varMap); err != nil {
log.Fatalf("Invalid JSON: %s", err)
}
input, err := os.Open(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
defer input.Close()
output := os.Stdout
if len(*outFile) > 0 && *outFile != "-" {
if output, err = os.Create(*outFile); err != nil {
log.Fatal(err)
}
defer output.Close()
}
if err := transform(input, output); err != nil {
log.Fatalf("Error processing file: %s", err)
}
}