2017-07-19 03:26:14 +02:00
|
|
|
// Copyright 2017 Google Inc. 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.
|
|
|
|
|
|
|
|
// Microfactory is a tool to incrementally compile a go program. It's similar
|
|
|
|
// to `go install`, but doesn't require a GOPATH. A package->path mapping can
|
|
|
|
// be specified as command line options:
|
|
|
|
//
|
|
|
|
// -pkg-path android/soong=build/soong
|
|
|
|
// -pkg-path github.com/google/blueprint=build/blueprint
|
|
|
|
//
|
|
|
|
// The paths can be relative to the current working directory, or an absolute
|
|
|
|
// path. Both packages and paths are compared with full directory names, so the
|
|
|
|
// android/soong-test package wouldn't be mapped in the above case.
|
|
|
|
//
|
|
|
|
// Microfactory will ignore *_test.go files, and limits *_darwin.go and
|
|
|
|
// *_linux.go files to MacOS and Linux respectively. It does not support build
|
|
|
|
// tags or any other suffixes.
|
|
|
|
//
|
|
|
|
// Builds are incremental by package. All input files are hashed, and if the
|
|
|
|
// hash of an input or dependency changes, the package is rebuilt.
|
|
|
|
//
|
|
|
|
// It also exposes the -trimpath option from go's compiler so that embedded
|
|
|
|
// path names (such as in log.Llongfile) are relative paths instead of absolute
|
|
|
|
// paths.
|
|
|
|
//
|
|
|
|
// If you don't have a previously built version of Microfactory, when used with
|
2017-08-08 22:01:31 +02:00
|
|
|
// -b <microfactory_bin_file>, Microfactory can rebuild itself as necessary.
|
|
|
|
// Combined with a shell script like microfactory.bash that uses `go run` to
|
|
|
|
// run Microfactory for the first time, go programs can be quickly bootstrapped
|
|
|
|
// entirely from source (and a standard go distribution).
|
2017-08-08 22:14:40 +02:00
|
|
|
package microfactory
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/sha1"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
2017-08-24 01:23:41 +02:00
|
|
|
"go/build"
|
2017-07-19 03:26:14 +02:00
|
|
|
"go/parser"
|
|
|
|
"go/token"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
goToolDir = filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH)
|
|
|
|
goVersion = findGoVersion()
|
2017-07-25 05:28:41 +02:00
|
|
|
isGo18 = strings.Contains(goVersion, "go1.8")
|
2017-07-19 03:26:14 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func findGoVersion() string {
|
|
|
|
if version, err := ioutil.ReadFile(filepath.Join(runtime.GOROOT(), "VERSION")); err == nil {
|
|
|
|
return string(version)
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "version")
|
|
|
|
if version, err := cmd.Output(); err == nil {
|
|
|
|
return string(version)
|
|
|
|
} else {
|
|
|
|
panic(fmt.Sprintf("Unable to discover go version: %v", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
type Config struct {
|
|
|
|
Race bool
|
|
|
|
Verbose bool
|
|
|
|
|
|
|
|
TrimPath string
|
|
|
|
|
2017-08-08 21:47:15 +02:00
|
|
|
TraceFunc func(name string) func()
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
pkgs []string
|
|
|
|
paths map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) Map(pkgPrefix, pathPrefix string) error {
|
|
|
|
if c.paths == nil {
|
|
|
|
c.paths = make(map[string]string)
|
|
|
|
}
|
|
|
|
if _, ok := c.paths[pkgPrefix]; ok {
|
|
|
|
return fmt.Errorf("Duplicate package prefix: %q", pkgPrefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.pkgs = append(c.pkgs, pkgPrefix)
|
|
|
|
c.paths[pkgPrefix] = pathPrefix
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path takes a package name, applies the path mappings and returns the resulting path.
|
|
|
|
//
|
|
|
|
// If the package isn't mapped, we'll return false to prevent compilation attempts.
|
|
|
|
func (c *Config) Path(pkg string) (string, bool, error) {
|
|
|
|
if c == nil || c.paths == nil {
|
|
|
|
return "", false, fmt.Errorf("No package mappings")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, pkgPrefix := range c.pkgs {
|
|
|
|
if pkg == pkgPrefix {
|
|
|
|
return c.paths[pkgPrefix], true, nil
|
|
|
|
} else if strings.HasPrefix(pkg, pkgPrefix+"/") {
|
|
|
|
return filepath.Join(c.paths[pkgPrefix], strings.TrimPrefix(pkg, pkgPrefix+"/")), true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", false, nil
|
|
|
|
}
|
|
|
|
|
2017-08-08 21:47:15 +02:00
|
|
|
func (c *Config) trace(format string, a ...interface{}) func() {
|
|
|
|
if c.TraceFunc == nil {
|
|
|
|
return func() {}
|
|
|
|
}
|
|
|
|
s := strings.TrimSpace(fmt.Sprintf(format, a...))
|
|
|
|
return c.TraceFunc(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func un(f func()) {
|
|
|
|
f()
|
|
|
|
}
|
|
|
|
|
2017-07-19 03:26:14 +02:00
|
|
|
type GoPackage struct {
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// Inputs
|
|
|
|
directDeps []*GoPackage // specified directly by the module
|
|
|
|
allDeps []*GoPackage // direct dependencies and transitive dependencies
|
|
|
|
files []string
|
|
|
|
|
|
|
|
// Outputs
|
|
|
|
pkgDir string
|
|
|
|
output string
|
|
|
|
hashResult []byte
|
|
|
|
|
|
|
|
// Status
|
|
|
|
mutex sync.Mutex
|
|
|
|
compiled bool
|
|
|
|
failed error
|
|
|
|
rebuilt bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// LinkedHashMap<string, GoPackage>
|
|
|
|
type linkedDepSet struct {
|
|
|
|
packageSet map[string](*GoPackage)
|
|
|
|
packageList []*GoPackage
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDepSet() *linkedDepSet {
|
|
|
|
return &linkedDepSet{packageSet: make(map[string]*GoPackage)}
|
|
|
|
}
|
|
|
|
func (s *linkedDepSet) tryGetByName(name string) (*GoPackage, bool) {
|
|
|
|
pkg, contained := s.packageSet[name]
|
|
|
|
return pkg, contained
|
|
|
|
}
|
|
|
|
func (s *linkedDepSet) getByName(name string) *GoPackage {
|
|
|
|
pkg, _ := s.tryGetByName(name)
|
|
|
|
return pkg
|
|
|
|
}
|
|
|
|
func (s *linkedDepSet) add(name string, goPackage *GoPackage) {
|
|
|
|
s.packageSet[name] = goPackage
|
|
|
|
s.packageList = append(s.packageList, goPackage)
|
|
|
|
}
|
|
|
|
func (s *linkedDepSet) ignore(name string) {
|
|
|
|
s.packageSet[name] = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindDeps searches all applicable go files in `path`, parses all of them
|
|
|
|
// for import dependencies that exist in pkgMap, then recursively does the
|
|
|
|
// same for all of those dependencies.
|
2017-08-08 00:01:19 +02:00
|
|
|
func (p *GoPackage) FindDeps(config *Config, path string) error {
|
2017-08-08 21:47:15 +02:00
|
|
|
defer un(config.trace("findDeps"))
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
depSet := newDepSet()
|
2017-08-08 00:01:19 +02:00
|
|
|
err := p.findDeps(config, path, depSet)
|
2017-07-19 03:26:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
p.allDeps = depSet.packageList
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-24 01:23:41 +02:00
|
|
|
// Roughly equivalent to go/build.Context.match
|
|
|
|
func matchBuildTag(name string) bool {
|
|
|
|
if name == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if i := strings.Index(name, ","); i >= 0 {
|
|
|
|
ok1 := matchBuildTag(name[:i])
|
|
|
|
ok2 := matchBuildTag(name[i+1:])
|
|
|
|
return ok1 && ok2
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(name, "!!") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(name, "!") {
|
|
|
|
return len(name) > 1 && !matchBuildTag(name[1:])
|
|
|
|
}
|
|
|
|
|
|
|
|
if name == runtime.GOOS || name == runtime.GOARCH || name == "gc" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, tag := range build.Default.BuildTags {
|
|
|
|
if tag == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, tag := range build.Default.ReleaseTags {
|
|
|
|
if tag == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseBuildComment(comment string) (matches, ok bool) {
|
|
|
|
if !strings.HasPrefix(comment, "//") {
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
for i, c := range comment {
|
|
|
|
if i < 2 || c == ' ' || c == '\t' {
|
|
|
|
continue
|
|
|
|
} else if c == '+' {
|
|
|
|
f := strings.Fields(comment[i:])
|
|
|
|
if f[0] == "+build" {
|
|
|
|
matches = false
|
|
|
|
for _, tok := range f[1:] {
|
|
|
|
matches = matches || matchBuildTag(tok)
|
|
|
|
}
|
|
|
|
return matches, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return false, false
|
|
|
|
}
|
|
|
|
|
2017-07-19 03:26:14 +02:00
|
|
|
// findDeps is the recursive version of FindDeps. allPackages is the map of
|
|
|
|
// all locally defined packages so that the same dependency of two different
|
|
|
|
// packages is only resolved once.
|
2017-08-08 00:01:19 +02:00
|
|
|
func (p *GoPackage) findDeps(config *Config, path string, allPackages *linkedDepSet) error {
|
2017-07-19 03:26:14 +02:00
|
|
|
// If this ever becomes too slow, we can look at reading the files once instead of twice
|
|
|
|
// But that just complicates things today, and we're already really fast.
|
|
|
|
foundPkgs, err := parser.ParseDir(token.NewFileSet(), path, func(fi os.FileInfo) bool {
|
|
|
|
name := fi.Name()
|
|
|
|
if fi.IsDir() || strings.HasSuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if runtime.GOOS != "darwin" && strings.HasSuffix(name, "_darwin.go") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if runtime.GOOS != "linux" && strings.HasSuffix(name, "_linux.go") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
2017-08-24 01:23:41 +02:00
|
|
|
}, parser.ImportsOnly|parser.ParseComments)
|
2017-07-19 03:26:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error parsing directory %q: %v", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var foundPkg *ast.Package
|
|
|
|
// foundPkgs is a map[string]*ast.Package, but we only want one package
|
|
|
|
if len(foundPkgs) != 1 {
|
|
|
|
return fmt.Errorf("Expected one package in %q, got %d", path, len(foundPkgs))
|
|
|
|
}
|
|
|
|
// Extract the first (and only) entry from the map.
|
|
|
|
for _, pkg := range foundPkgs {
|
|
|
|
foundPkg = pkg
|
|
|
|
}
|
|
|
|
|
|
|
|
var deps []string
|
|
|
|
localDeps := make(map[string]bool)
|
|
|
|
|
|
|
|
for filename, astFile := range foundPkg.Files {
|
2017-08-24 01:23:41 +02:00
|
|
|
ignore := false
|
|
|
|
for _, commentGroup := range astFile.Comments {
|
|
|
|
for _, comment := range commentGroup.List {
|
|
|
|
if matches, ok := parseBuildComment(comment.Text); ok && !matches {
|
|
|
|
ignore = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ignore {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-07-19 03:26:14 +02:00
|
|
|
p.files = append(p.files, filename)
|
|
|
|
|
|
|
|
for _, importSpec := range astFile.Imports {
|
|
|
|
name, err := strconv.Unquote(importSpec.Path.Value)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: invalid quoted string: <%s> %v", filename, importSpec.Path.Value, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if pkg, ok := allPackages.tryGetByName(name); ok {
|
|
|
|
if pkg != nil {
|
|
|
|
if _, ok := localDeps[name]; !ok {
|
|
|
|
deps = append(deps, name)
|
|
|
|
localDeps[name] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var pkgPath string
|
2017-08-08 00:01:19 +02:00
|
|
|
if path, ok, err := config.Path(name); err != nil {
|
2017-07-19 03:26:14 +02:00
|
|
|
return err
|
|
|
|
} else if !ok {
|
|
|
|
// Probably in the stdlib, but if not, then the compiler will fail with a reasonable error message
|
|
|
|
// Mark it as such so that we don't try to decode its path again.
|
|
|
|
allPackages.ignore(name)
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
pkgPath = path
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg := &GoPackage{
|
|
|
|
Name: name,
|
|
|
|
}
|
|
|
|
deps = append(deps, name)
|
|
|
|
allPackages.add(name, pkg)
|
|
|
|
localDeps[name] = true
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
if err := pkg.findDeps(config, pkgPath, allPackages); err != nil {
|
2017-07-19 03:26:14 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(p.files)
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.Verbose {
|
2017-07-19 03:26:14 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "Package %q depends on %v\n", p.Name, deps)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(deps)
|
|
|
|
for _, dep := range deps {
|
|
|
|
p.directDeps = append(p.directDeps, allPackages.getByName(dep))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
func (p *GoPackage) Compile(config *Config, outDir string) error {
|
2017-07-19 03:26:14 +02:00
|
|
|
p.mutex.Lock()
|
|
|
|
defer p.mutex.Unlock()
|
|
|
|
if p.compiled {
|
|
|
|
return p.failed
|
|
|
|
}
|
|
|
|
p.compiled = true
|
|
|
|
|
|
|
|
// Build all dependencies in parallel, then fail if any of them failed.
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, dep := range p.directDeps {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(dep *GoPackage) {
|
|
|
|
defer wg.Done()
|
2017-08-08 00:01:19 +02:00
|
|
|
dep.Compile(config, outDir)
|
2017-07-19 03:26:14 +02:00
|
|
|
}(dep)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
for _, dep := range p.directDeps {
|
|
|
|
if dep.failed != nil {
|
|
|
|
p.failed = dep.failed
|
|
|
|
return p.failed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-08 21:47:15 +02:00
|
|
|
endTrace := config.trace("check compile %s", p.Name)
|
2017-07-19 03:26:14 +02:00
|
|
|
|
2017-07-19 04:37:37 +02:00
|
|
|
p.pkgDir = filepath.Join(outDir, strings.Replace(p.Name, "/", "-", -1))
|
2017-07-19 03:26:14 +02:00
|
|
|
p.output = filepath.Join(p.pkgDir, p.Name) + ".a"
|
|
|
|
shaFile := p.output + ".hash"
|
|
|
|
|
|
|
|
hash := sha1.New()
|
|
|
|
fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)
|
|
|
|
|
|
|
|
cmd := exec.Command(filepath.Join(goToolDir, "compile"),
|
2021-03-02 10:09:34 +01:00
|
|
|
"-N", "-l", // Disable optimization and inlining so that debugging works better
|
2017-07-19 03:26:14 +02:00
|
|
|
"-o", p.output,
|
|
|
|
"-p", p.Name,
|
|
|
|
"-complete", "-pack", "-nolocalimports")
|
2018-04-30 23:44:22 +02:00
|
|
|
if !isGo18 && !config.Race {
|
2017-07-25 05:28:41 +02:00
|
|
|
cmd.Args = append(cmd.Args, "-c", fmt.Sprintf("%d", runtime.NumCPU()))
|
|
|
|
}
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.Race {
|
2017-07-19 03:26:14 +02:00
|
|
|
cmd.Args = append(cmd.Args, "-race")
|
|
|
|
fmt.Fprintln(hash, "-race")
|
|
|
|
}
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.TrimPath != "" {
|
|
|
|
cmd.Args = append(cmd.Args, "-trimpath", config.TrimPath)
|
|
|
|
fmt.Fprintln(hash, config.TrimPath)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
|
|
|
for _, dep := range p.directDeps {
|
|
|
|
cmd.Args = append(cmd.Args, "-I", dep.pkgDir)
|
|
|
|
hash.Write(dep.hashResult)
|
|
|
|
}
|
|
|
|
for _, filename := range p.files {
|
|
|
|
cmd.Args = append(cmd.Args, filename)
|
|
|
|
fmt.Fprintln(hash, filename)
|
|
|
|
|
|
|
|
// Hash the contents of the input files
|
|
|
|
f, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
f.Close()
|
|
|
|
err = fmt.Errorf("%s: %v", filename, err)
|
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = io.Copy(hash, f)
|
|
|
|
if err != nil {
|
|
|
|
f.Close()
|
|
|
|
err = fmt.Errorf("%s: %v", filename, err)
|
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
}
|
|
|
|
p.hashResult = hash.Sum(nil)
|
|
|
|
|
|
|
|
var rebuild bool
|
|
|
|
if _, err := os.Stat(p.output); err != nil {
|
|
|
|
rebuild = true
|
|
|
|
}
|
|
|
|
if !rebuild {
|
|
|
|
if oldSha, err := ioutil.ReadFile(shaFile); err == nil {
|
|
|
|
rebuild = !bytes.Equal(oldSha, p.hashResult)
|
|
|
|
} else {
|
|
|
|
rebuild = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
endTrace()
|
|
|
|
if !rebuild {
|
|
|
|
return nil
|
|
|
|
}
|
2017-08-08 21:47:15 +02:00
|
|
|
defer un(config.trace("compile %s", p.Name))
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
err := os.RemoveAll(p.pkgDir)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("%s: %v", p.Name, err)
|
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.MkdirAll(filepath.Dir(p.output), 0777)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("%s: %v", p.Name, err)
|
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Stdin = nil
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.Verbose {
|
2017-07-19 03:26:14 +02:00
|
|
|
fmt.Fprintln(os.Stderr, cmd.Args)
|
|
|
|
}
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
2017-11-02 21:41:10 +01:00
|
|
|
commandText := strings.Join(cmd.Args, " ")
|
|
|
|
err = fmt.Errorf("%q: %v", commandText, err)
|
2017-07-19 03:26:14 +02:00
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(shaFile, p.hashResult, 0666)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("%s: %v", p.Name, err)
|
|
|
|
p.failed = err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
p.rebuilt = true
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
func (p *GoPackage) Link(config *Config, out string) error {
|
2017-07-19 03:26:14 +02:00
|
|
|
if p.Name != "main" {
|
|
|
|
return fmt.Errorf("Can only link main package")
|
|
|
|
}
|
2017-08-08 21:47:15 +02:00
|
|
|
endTrace := config.trace("check link %s", p.Name)
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
shaFile := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_hash")
|
|
|
|
|
|
|
|
if !p.rebuilt {
|
|
|
|
if _, err := os.Stat(out); err != nil {
|
|
|
|
p.rebuilt = true
|
|
|
|
} else if oldSha, err := ioutil.ReadFile(shaFile); err != nil {
|
|
|
|
p.rebuilt = true
|
|
|
|
} else {
|
|
|
|
p.rebuilt = !bytes.Equal(oldSha, p.hashResult)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
endTrace()
|
|
|
|
if !p.rebuilt {
|
|
|
|
return nil
|
|
|
|
}
|
2017-08-08 21:47:15 +02:00
|
|
|
defer un(config.trace("link %s", p.Name))
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
err := os.Remove(shaFile)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = os.Remove(out)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command(filepath.Join(goToolDir, "link"), "-o", out)
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.Race {
|
2017-07-19 03:26:14 +02:00
|
|
|
cmd.Args = append(cmd.Args, "-race")
|
|
|
|
}
|
|
|
|
for _, dep := range p.allDeps {
|
|
|
|
cmd.Args = append(cmd.Args, "-L", dep.pkgDir)
|
|
|
|
}
|
|
|
|
cmd.Args = append(cmd.Args, p.output)
|
|
|
|
cmd.Stdin = nil
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
2017-08-08 00:01:19 +02:00
|
|
|
if config.Verbose {
|
2017-07-19 03:26:14 +02:00
|
|
|
fmt.Fprintln(os.Stderr, cmd.Args)
|
|
|
|
}
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("command %s failed with error %v", cmd.Args, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile(shaFile, p.hashResult, 0666)
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
func Build(config *Config, out, pkg string) (*GoPackage, error) {
|
|
|
|
p := &GoPackage{
|
|
|
|
Name: "main",
|
|
|
|
}
|
|
|
|
|
2017-10-18 23:15:24 +02:00
|
|
|
lockFileName := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+".lock")
|
|
|
|
lockFile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0666)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error creating lock file (%q): %v", lockFileName, err)
|
|
|
|
}
|
|
|
|
defer lockFile.Close()
|
|
|
|
|
|
|
|
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error locking file (%q): %v", lockFileName, err)
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
path, ok, err := config.Path(pkg)
|
2017-08-08 22:01:31 +02:00
|
|
|
if err != nil {
|
2017-08-08 00:01:19 +02:00
|
|
|
return nil, fmt.Errorf("Error finding package %q for main: %v", pkg, err)
|
2017-08-08 22:01:31 +02:00
|
|
|
}
|
|
|
|
if !ok {
|
2017-08-08 00:01:19 +02:00
|
|
|
return nil, fmt.Errorf("Could not find package %q", pkg)
|
2017-08-08 22:01:31 +02:00
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
intermediates := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_intermediates")
|
|
|
|
if err := os.MkdirAll(intermediates, 0777); err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to create intermediates directory: %v", err)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
if err := p.FindDeps(config, path); err != nil {
|
2017-11-02 21:41:10 +01:00
|
|
|
return nil, fmt.Errorf("Failed to find deps of %v: %v", pkg, err)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
2017-08-08 00:01:19 +02:00
|
|
|
if err := p.Compile(config, intermediates); err != nil {
|
2017-11-02 21:41:10 +01:00
|
|
|
return nil, fmt.Errorf("Failed to compile %v: %v", pkg, err)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
2017-08-08 00:01:19 +02:00
|
|
|
if err := p.Link(config, out); err != nil {
|
2017-11-02 21:41:10 +01:00
|
|
|
return nil, fmt.Errorf("Failed to link %v: %v", pkg, err)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
2017-08-08 00:01:19 +02:00
|
|
|
return p, nil
|
|
|
|
}
|
2017-07-19 03:26:14 +02:00
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
// rebuildMicrofactory checks to see if microfactory itself needs to be rebuilt,
|
|
|
|
// and if does, it will launch a new copy and return true. Otherwise it will return
|
|
|
|
// false to continue executing.
|
|
|
|
func rebuildMicrofactory(config *Config, mybin string) bool {
|
|
|
|
if pkg, err := Build(config, mybin, "github.com/google/blueprint/microfactory/main"); err != nil {
|
2017-07-19 03:26:14 +02:00
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
os.Exit(1)
|
2017-08-08 00:01:19 +02:00
|
|
|
} else if !pkg.rebuilt {
|
2017-07-19 03:26:14 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command(mybin, os.Args[1:]...)
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err == nil {
|
|
|
|
return true
|
|
|
|
} else if e, ok := err.(*exec.ExitError); ok {
|
|
|
|
os.Exit(e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
|
|
|
|
}
|
|
|
|
os.Exit(1)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-08-08 22:14:40 +02:00
|
|
|
// microfactory.bash will make a copy of this file renamed into the main package for use with `go run`
|
|
|
|
func main() { Main() }
|
|
|
|
func Main() {
|
2017-08-08 00:01:19 +02:00
|
|
|
var output, mybin string
|
|
|
|
var config Config
|
|
|
|
pkgMap := pkgPathMappingVar{&config}
|
2017-07-19 03:26:14 +02:00
|
|
|
|
|
|
|
flags := flag.NewFlagSet("", flag.ExitOnError)
|
2017-08-08 00:01:19 +02:00
|
|
|
flags.BoolVar(&config.Race, "race", false, "enable data race detection.")
|
|
|
|
flags.BoolVar(&config.Verbose, "v", false, "Verbose")
|
2017-07-19 03:26:14 +02:00
|
|
|
flags.StringVar(&output, "o", "", "Output file")
|
|
|
|
flags.StringVar(&mybin, "b", "", "Microfactory binary location")
|
2017-08-08 00:01:19 +02:00
|
|
|
flags.StringVar(&config.TrimPath, "trimpath", "", "remove prefix from recorded source file paths")
|
2017-07-19 03:26:14 +02:00
|
|
|
flags.Var(&pkgMap, "pkg-path", "Mapping of package prefixes to file paths")
|
|
|
|
err := flags.Parse(os.Args[1:])
|
|
|
|
|
|
|
|
if err == flag.ErrHelp || flags.NArg() != 1 || output == "" {
|
|
|
|
fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "-o out/binary <main-package>")
|
|
|
|
flags.PrintDefaults()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
tracePath := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+".trace")
|
2017-08-08 21:47:15 +02:00
|
|
|
if traceFile, err := os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666); err == nil {
|
|
|
|
defer traceFile.Close()
|
|
|
|
config.TraceFunc = func(name string) func() {
|
|
|
|
fmt.Fprintf(traceFile, "%d B %s\n", time.Now().UnixNano()/1000, name)
|
|
|
|
return func() {
|
|
|
|
fmt.Fprintf(traceFile, "%d E %s\n", time.Now().UnixNano()/1000, name)
|
|
|
|
}
|
|
|
|
}
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
|
|
|
if executable, err := os.Executable(); err == nil {
|
2017-08-08 21:47:15 +02:00
|
|
|
defer un(config.trace("microfactory %s", executable))
|
2017-07-19 03:26:14 +02:00
|
|
|
} else {
|
2017-08-08 21:47:15 +02:00
|
|
|
defer un(config.trace("microfactory <unknown>"))
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|
|
|
|
|
2017-08-08 22:01:31 +02:00
|
|
|
if mybin != "" {
|
2017-08-08 00:01:19 +02:00
|
|
|
if rebuildMicrofactory(&config, mybin) {
|
2017-07-19 03:26:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
if _, err := Build(&config, output, flags.Arg(0)); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
2017-07-19 03:26:14 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
|
|
|
|
// <package-prefix>=<path-prefix> mappings.
|
2017-08-08 00:01:19 +02:00
|
|
|
type pkgPathMappingVar struct{ *Config }
|
2017-07-19 03:26:14 +02:00
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
func (pkgPathMappingVar) String() string {
|
2017-07-19 03:26:14 +02:00
|
|
|
return "<package-prefix>=<path-prefix>"
|
|
|
|
}
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
func (p *pkgPathMappingVar) Set(value string) error {
|
2017-07-19 03:26:14 +02:00
|
|
|
equalPos := strings.Index(value, "=")
|
|
|
|
if equalPos == -1 {
|
|
|
|
return fmt.Errorf("Argument must be in the form of: %q", p.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
pkgPrefix := strings.TrimSuffix(value[:equalPos], "/")
|
|
|
|
pathPrefix := strings.TrimSuffix(value[equalPos+1:], "/")
|
|
|
|
|
2017-08-08 00:01:19 +02:00
|
|
|
return p.Map(pkgPrefix, pathPrefix)
|
2017-07-19 03:26:14 +02:00
|
|
|
}
|