blob: b9aa1037138f1b20f92a09ae66b16dfd795f93fa [file] [log] [blame]
// Copyright 2016 The Chromium 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 genCmd which parses a set of .mojom files and runs the
// code generators in order to emit generated bindings for the provided files.
//
// The gen command is invoked as follows:
//
// mojom gen [-I <include_dirs>]
// [--output-dir <path_to_out>]
// [--src-root-path <path_to_src_root>]
// [--generators <generator_list>]
// [--no-gen-imports]
// [--gen-arg <extra_parameter>[=<value>]]
// <mojom_file>...
//
// <include_dirs> is the comma-separated list of directory paths to search for mojom imports.
// <path_to_out> is the root directory in which generators should place generated files.
// <path_to_src_root> is the path to the root of the source tree. The generators
// use this to compute the directory layout of the generated files.
// <generators> is a comma-separated list of generators to run. Each generator
// should be the name of an executable that obeys the generator contract.
// These executables must be found on the *generator path*
// which is described below.
// --gen-arg is used to pass extra arguments to the generators. For example
// "--gen-arg generate-type-info" will cause the flag "--generate-type-info" to
// be passed to each of the generators and "--gen-arg debug-level=3" will cause
// --debug-level=3" to be passed to each of the generators.
// <mojom_file>... is one or more paths to .mojom files to be parsed.
//
// If --no-gen-imports is specified, it is passed on to the generators. It tells
// the generators that bindings must be generated only for files specified on
// the command line as opposed to files specified on the command lines and their
// imports recursively.
//
// The *generator path* is a list of absolute paths to directories. When looking up
// mojom generators, this command will look in those directories for executables
// matching the generator name. By default, the generator path is
// <directory_containing_mojom_tool>/generators.
// If the MOJOM_GENERATORS environment variable is set, it is assumed to contain
// a semicolon-separated list of absolute paths which are used as the generator
// path.
//
// If there are no errors, the program returns status 0 and writes nothing to
// stderr or stdout. Otherwise, errors found by the parser or the generators
// will be printed to stderr and a non-zero status code will be returned.
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
const (
mojomGeneratorsEnv = "MOJOM_GENERATORS"
)
// genCmd implements the gen command for the mojom tool.
// The slice of strings |args| is the list of arguments passed on the command
// line starting with the program name and followed by the invoked command.
func genCmd(args []string) {
flagSet := flag.NewFlagSet("gen", flag.ContinueOnError)
var importPaths DirectoryList
flagSet.Var(&importPaths, "I",
"comma-separated list of directory paths to search for mojom imports")
outputDir := flagSet.String("output-dir", ".",
"output directory for generated files.")
srcRootPath := flagSet.String("src-root-path", ".",
"relative path to the root of the source tree. The generators use the "+
"value of this argument to compute the directory layout of the generated files.")
noGenImports := flagSet.Bool("no-gen-imports", false,
"Generate code only for the files that are specified on the command line. "+
"By default, code is generated for all specified files and their imports recursively.")
var generatorNames CommaSeparatedList
flagSet.Var(&generatorNames, "generators", "Comma-separated list of generators")
flagSet.SetOutput(ioutil.Discard)
var generatorArgs RepeatedStringArg
flagSet.Var(&generatorArgs, "gen-arg",
"Argument to be passed to the generators. Use more than once to pass more "+
"than one argument to the generators. (e.g. --gen-arg foo=bar --gen-arg blah)")
printUsage := func() {
fmt.Fprintf(os.Stderr, "Usage: %s gen [-I <include_dirs>] "+
"[--output-dir <path_to_dir>] "+
"[--src-root-path <path_to_src_root>] "+
"[--generators <generator_list>] "+
"[--no-gen-imports] "+
"[--gen-arg <generator_argument>[=<value>]] "+
"<mojom_file>\n\n", filepath.Base(args[0]))
fmt.Fprintf(os.Stderr, "The gen command parses a list of mojom files and "+
"runs code generators in order to emit generated bindings for the provided files.\n\n")
fmt.Fprintf(os.Stderr, "The generators specified on the command line should "+
"be the names of executables found on the *generator path*\n"+
"By default, the generator path is <directory_containing_mojom_tool>/generators.\n"+
"If the MOJOM_GENERATORS environment variable is set, it is assumed to contain "+
"a semicolon-separated list of absolute paths which are used as the generator path.\n\n")
fmt.Fprintf(os.Stderr, UsageString(flagSet))
}
if err := flagSet.Parse(args[2:]); err != nil {
if err != flag.ErrHelp {
fmt.Fprintln(os.Stderr, err.Error())
}
printUsage()
os.Exit(1)
}
generatorPaths := findGeneratorPaths(mojomGenPaths(), generatorNames)
fileNames := flagSet.Args()
mojomBytes := parse(fileNames, importPaths, printUsage, false, false)
for _, generatorPath := range generatorPaths {
// Consider running the generators in parallel.
runGenerator(generatorPath, mojomBytes, *noGenImports, *srcRootPath, *outputDir, generatorArgs)
}
}
func runGenerator(generatorPath string, mojomBytes []byte, noGenImports bool, srcRootPath string, outputDir string, generatorArgs []string) {
var args []string
args = append(args, "--file-graph", "-")
args = append(args, "--output-dir", outputDir)
args = append(args, "--src-root-path", srcRootPath)
for _, generatorArg := range generatorArgs {
args = append(args, "--"+generatorArg)
}
if noGenImports {
args = append(args, "--no-gen-imports")
}
cmd := exec.Command(generatorPath, args...)
// We get pipes for both stdin and stderr before starting the command.
stdinWriter, err := cmd.StdinPipe()
if err != nil {
log.Fatalln(err.Error())
}
var stdErrReader io.ReadCloser
stdErrReader, err = cmd.StderrPipe()
if err != nil {
log.Fatalln(err.Error())
}
err = cmd.Start()
if err != nil {
log.Fatalln(err.Error())
}
// We write the serialized mojom to the command's stdin and send an eof on
// that pipe before anything else.
_, err = stdinWriter.Write(mojomBytes)
if err != nil {
log.Fatalln(err.Error())
}
if err := stdinWriter.Close(); err != nil {
log.Fatalln(err.Error())
}
// We collect whatever was written to stderr.
var stdErr []byte
stdErr, err = ioutil.ReadAll(stdErrReader)
if err != nil {
log.Fatalln(err.Error())
}
if err := stdErrReader.Close(); err != nil {
log.Fatalln(err.Error())
}
if err := cmd.Wait(); err != nil {
// If stderr is empty, we print stderr otherwise, we use the error message.
if len(stdErr) > 0 {
log.Fatal(string(stdErr))
} else {
log.Fatalln(err.Error())
}
}
}
// mojomGenPaths returns a list of absolute paths where generators should be
// located.
//
// If the environment variable MOJOM_GENERATORS is empty, the generators are
// assumed to be located at {path-to-mojom-tool-dir}/generators.
//
// Otherwise, MOJOM_GENERATORS is expected to contain a semi-colon separated
// list of absolute paths.
func mojomGenPaths() []string {
env := os.Getenv(mojomGeneratorsEnv)
if env == "" {
mojomToolPath, _ := filepath.Abs(filepath.Join(os.Args[0]))
generatorsPath := filepath.Join(filepath.Dir(mojomToolPath), "generators")
return []string{generatorsPath}
}
genPaths := strings.Split(env, ";")
for _, genPath := range genPaths {
if !filepath.IsAbs(genPath) {
log.Fatalf("%s is not an absolute path!", genPath)
}
}
return genPaths
}
// findGeneratorPaths searches through the directories listed in |paths| files
// named after |generator| and returns the path to those files.
func findGeneratorPaths(paths []string, generators []string) (generatorPaths []string) {
for _, generator := range generators {
generatorPath := findGeneratorPath(paths, generator)
if generatorPath != "" {
generatorPaths = append(generatorPaths, generatorPath)
}
}
return
}
// findGeneratorPath searches through the directories listed in |paths| for a
// file named |generatorName| and returns the path to that file. If no such file
// is found, the empty string is returned.
func findGeneratorPath(paths []string, generatorName string) string {
for _, path := range paths {
genPath := filepath.Join(path, generatorName)
if fileInfo, err := os.Stat(genPath); err == nil && !fileInfo.IsDir() {
return genPath
}
}
return ""
}