blob: d8b345d3ba1ef313d4932d91cf9f0ec5df867dea [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains flag definitions for the source_generator package as well
// as all the functions which make direct use of these flags.
package source_generator
import (
"config"
"flag"
"fmt"
"os"
"strconv"
"strings"
)
var (
addFileSuffix = flag.Bool("add_file_suffix", false, "Append the out_format to the out_file, even if there is only one out_format specified")
outFile = flag.String("output_file", "", "File to which the serialized config should be written. Defaults to stdout. When multiple output formats are specified, it will append the format to the filename")
outFilename = flag.String("out_filename", "", "The base name to use for writing files. Should not be used with output_file.")
outDir = flag.String("out_dir", "", "The directory into which files should be written.")
outFormat = flag.String("out_format", "bin", "Specifies the output formats (separated by ' '). Supports 'bin' (serialized proto), 'go' (a golang package), 'b64' (serialized proto to base 64), 'cpp' (a C++ file containing a variable with a base64-encoded serialized proto) 'dart' (a Dart library), 'json' (a JSON object), and 'rust' (a rust crate)")
features = flag.String("features", "", "A comma separated list of source generator features to enable.")
namespace = flag.String("namespace", "", "When using the 'cpp', 'rust', or 'go' output format, this will specify the period-separated namespace within which the config variable must be placed (this will be transformed into an underscore-separated package name for go).")
goPackageName = flag.String("go_package", "", "When using the 'go' output format, this will specify the package for generated code.")
dartOutDir = flag.String("dart_out_dir", "", "The directory to write dart files to (if different from out_dir)")
varName = flag.String("var_name", "config", "When using the 'cpp' or 'dart' output format, this will specify the variable name to be used in the output.")
privacyParamsPath = flag.String("privacy_params_path", "", "For output formats that require an error estimate, this specifies the path to a file containing required privacy parameters.")
checkOnly = flag.Bool("check_only", false, "Only check that the configuration is valid.")
allowEmptyOutput = flag.Bool("allow_empty_output", false, "Relax the requirement that the cobalt registry output not be empty.")
dimensionNameMapsForMetricIds = flag.String("dimension_name_maps_for_metric_ids", "", "A comma separated list of metric_ids for which name maps should be generated")
)
// checkFlags verifies that the specified flags are compatible with each other.
func checkFlags() error {
if *outFile != "" && *checkOnly {
return fmt.Errorf("'output_file' does not make sense if 'check_only' is set.")
}
if *outFile != "" && *outFilename != "" {
return fmt.Errorf("-output_file and -out_filename are mutually exclusive.")
}
if (*outDir != "" || *dartOutDir != "") && *outFile != "" {
return fmt.Errorf("-output_file should not be set at the same time as -out_dir or -dart_out_dir")
}
if (*outDir != "" || *dartOutDir != "") && *outFilename == "" {
return fmt.Errorf("-out_dir or -dart_out_dir require specifying -out_filename.")
}
for _, format := range parseOutFormatList(*outFormat) {
if format == "go" {
if *goPackageName == "" {
return fmt.Errorf("-go_package must be specified for the go output format")
}
}
}
return nil
}
type generatorOptions struct {
enabledFeatures map[string]bool
dimensionNameMapsForMetricIds map[uint32]bool
}
func newGeneratorOptions(featuresIn, dimensionNameMapsForMetricIdsIn string) (*generatorOptions, error) {
features := strings.Split(featuresIn, ",")
enabledFeatures := map[string]bool{}
for _, feature := range features {
enabledFeatures[feature] = true
}
dimensionNameMapsForMetricIdsList := make([]uint32, 0, len(strings.Split(dimensionNameMapsForMetricIdsIn, ",")))
for _, v := range strings.Split(dimensionNameMapsForMetricIdsIn, ",") {
if v == "" {
continue
}
i, err := strconv.Atoi(v)
if err != nil {
return nil, err
}
dimensionNameMapsForMetricIdsList = append(dimensionNameMapsForMetricIdsList, uint32(i))
}
dimensionNameMapsForMetricIds := map[uint32]bool{}
for _, metricId := range dimensionNameMapsForMetricIdsList {
dimensionNameMapsForMetricIds[metricId] = true
}
return &generatorOptions{enabledFeatures: enabledFeatures, dimensionNameMapsForMetricIds: dimensionNameMapsForMetricIds}, nil
}
func (f *generatorOptions) isForTesting() bool {
return f.enabledFeatures["testing"]
}
func (f *generatorOptions) shouldGenerateConfigBase64() bool {
return f.enabledFeatures["generate-config-base64"]
}
func (f *generatorOptions) shouldGenerateNameMaps() bool {
return len(f.dimensionNameMapsForMetricIds) > 0
}
func (f *generatorOptions) shouldGenerateNameMapsFor(metricId uint32) bool {
return f.dimensionNameMapsForMetricIds[metricId]
}
func filenameGeneratorFromFlags() func(string) string {
return getFilenameGenerator(*outFile, *outFilename, *outDir, *dartOutDir)
}
// WriteDepFileFromFlags writes a depfile to the location specified in depFile.
// files must be a list of the reigstry input files.
func WriteDepFileFromFlags(files []string, depFile string) error {
if *outFile == "" && *outFilename == "" {
return fmt.Errorf("-dep_file requires -output_file or -out_filename")
}
w, err := os.Create(depFile)
if err != nil {
return err
}
defer w.Close()
return writeDepFile(parseOutFormatList(*outFormat), files, filenameGeneratorFromFlags(), w)
}
// WriteConfigFromFlags writes the specified CobaltRegistry according to the
// flags specified above.
func WriteConfigFromFlags(c, filtered *config.CobaltRegistry) error {
if err := checkFlags(); err != nil {
return err
}
generateFilename := filenameGeneratorFromFlags()
generatorOptions, err := newGeneratorOptions(*features, *dimensionNameMapsForMetricIds)
if err != nil {
return err
}
for _, format := range parseOutFormatList(*outFormat) {
outputFormatter, err := getOutputFormatter(format, *namespace, *goPackageName, *varName, *generatorOptions, *outFilename)
if err != nil {
return err
}
// Then, we serialize the configuration.
configBytes, err := outputFormatter(c, filtered)
if err != nil {
return err
}
// Check that the output file is not empty.
if !*allowEmptyOutput && len(configBytes) == 0 {
return fmt.Errorf("Output file is empty.")
}
// If no errors have occurred yet and checkOnly was set, we don't need to write anything.
if *checkOnly {
continue
}
// By default we print the output to stdout.
w := os.Stdout
if *outFile != "" || *outFilename != "" {
fname := generateFilename(format)
w, err = os.Create(fname)
if err != nil {
return err
}
}
if _, err := w.Write(configBytes); err != nil {
return err
}
}
if *checkOnly {
fmt.Printf("OK\n")
}
return nil
}