blob: 0650da365887e544eaa87d00045bf3faa5132810 [file] [log] [blame]
// Copyright 2017 The Bazel 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.
// compile compiles .go files with "go tool compile". It is invoked by the
// Go rules as an action.
package main
import (
"flag"
"fmt"
"go/build"
"go/parser"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
func abs(path string) string {
if abs, err := filepath.Abs(path); err != nil {
return path
} else {
return abs
}
}
func run(args []string) error {
sources := multiFlag{}
deps := multiFlag{}
search := multiFlag{}
flags := flag.NewFlagSet("compile", flag.ContinueOnError)
flags.Var(&sources, "src", "A source file to be filtered and compiled")
flags.Var(&deps, "dep", "Import path of a direct dependency")
flags.Var(&search, "I", "Search paths of a direct dependency")
trimpath := flags.String("trimpath", "", "The base of the paths to trim")
output := flags.String("o", "", "The output object file to write")
// process the args
if len(args) < 2 {
flags.Usage()
return fmt.Errorf("The go tool must be specified")
}
gotool := args[0]
if err := flags.Parse(args[1:]); err != nil {
return err
}
// apply build constraints to the source list
bctx := build.Default
bctx.CgoEnabled = true
sources, err := filterFiles(bctx, sources)
if err != nil {
return err
}
if len(sources) <= 0 {
return fmt.Errorf("no unfiltered sources to compile")
}
// Check that the filtered sources don't import anything outside of deps.
if err := checkDirectDeps(bctx, sources, deps); err != nil {
return err
}
goargs := []string{"tool", "compile"}
goargs = append(goargs, "-trimpath", abs(*trimpath))
for _, path := range search {
goargs = append(goargs, "-I", abs(path))
}
goargs = append(goargs, "-o", *output)
goargs = append(goargs, flags.Args()...)
goargs = append(goargs, sources...)
cmd := exec.Command(gotool, goargs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running compiler: %v", err)
}
return nil
}
func main() {
if err := run(os.Args[1:]); err != nil {
log.Fatal(err)
}
}
func checkDirectDeps(bctx build.Context, sources, deps []string) error {
depSet := make(map[string]bool)
for _, d := range deps {
depSet[d] = true
}
var errs depsError
fs := token.NewFileSet()
for _, s := range sources {
f, err := parser.ParseFile(fs, s, nil, parser.ImportsOnly)
if err != nil {
// Let the compiler report parse errors.
continue
}
for _, i := range f.Imports {
path, err := strconv.Unquote(i.Path.Value)
if err != nil {
// Should never happen, but let the compiler deal with it.
continue
}
if path == "C" || isStandard(bctx, path) || isRelative(path) {
// Standard paths don't need to be listed as dependencies (for now).
// Relative paths aren't supported yet. We don't emit errors here, but
// they will certainly break something else.
continue
}
if !depSet[path] {
errs = append(errs, fmt.Errorf("%s: import of %s, which is not a direct dependency", s, path))
}
}
}
if len(errs) > 0 {
return errs
}
return nil
}
type depsError []error
var _ error = depsError(nil)
func (e depsError) Error() string {
errorStrings := make([]string, len(e))
for i, err := range e {
errorStrings[i] = err.Error()
}
return "missing strict dependencies:\n\t" + strings.Join(errorStrings, "\n\t")
}
func isStandard(bctx build.Context, path string) bool {
rootPath := filepath.Join(bctx.GOROOT, "src", filepath.FromSlash(path))
st, err := os.Stat(rootPath)
return err == nil && st.IsDir()
}
func isRelative(path string) bool {
return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
}