Home | History | Annotate | Download | only in microfactory
      1 // Copyright 2017 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 // Microfactory is a tool to incrementally compile a go program. It's similar
     16 // to `go install`, but doesn't require a GOPATH. A package->path mapping can
     17 // be specified as command line options:
     18 //
     19 //   -pkg-path android/soong=build/soong
     20 //   -pkg-path github.com/google/blueprint=build/blueprint
     21 //
     22 // The paths can be relative to the current working directory, or an absolute
     23 // path. Both packages and paths are compared with full directory names, so the
     24 // android/soong-test package wouldn't be mapped in the above case.
     25 //
     26 // Microfactory will ignore *_test.go files, and limits *_darwin.go and
     27 // *_linux.go files to MacOS and Linux respectively. It does not support build
     28 // tags or any other suffixes.
     29 //
     30 // Builds are incremental by package. All input files are hashed, and if the
     31 // hash of an input or dependency changes, the package is rebuilt.
     32 //
     33 // It also exposes the -trimpath option from go's compiler so that embedded
     34 // path names (such as in log.Llongfile) are relative paths instead of absolute
     35 // paths.
     36 //
     37 // If you don't have a previously built version of Microfactory, when used with
     38 // -b <microfactory_bin_file>, Microfactory can rebuild itself as necessary.
     39 // Combined with a shell script like microfactory.bash that uses `go run` to
     40 // run Microfactory for the first time, go programs can be quickly bootstrapped
     41 // entirely from source (and a standard go distribution).
     42 package microfactory
     43 
     44 import (
     45 	"bytes"
     46 	"crypto/sha1"
     47 	"flag"
     48 	"fmt"
     49 	"go/ast"
     50 	"go/build"
     51 	"go/parser"
     52 	"go/token"
     53 	"io"
     54 	"io/ioutil"
     55 	"os"
     56 	"os/exec"
     57 	"path/filepath"
     58 	"runtime"
     59 	"sort"
     60 	"strconv"
     61 	"strings"
     62 	"sync"
     63 	"syscall"
     64 	"time"
     65 )
     66 
     67 var (
     68 	goToolDir = filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH)
     69 	goVersion = findGoVersion()
     70 	isGo18    = strings.Contains(goVersion, "go1.8")
     71 )
     72 
     73 func findGoVersion() string {
     74 	if version, err := ioutil.ReadFile(filepath.Join(runtime.GOROOT(), "VERSION")); err == nil {
     75 		return string(version)
     76 	}
     77 
     78 	cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "version")
     79 	if version, err := cmd.Output(); err == nil {
     80 		return string(version)
     81 	} else {
     82 		panic(fmt.Sprintf("Unable to discover go version: %v", err))
     83 	}
     84 }
     85 
     86 type Config struct {
     87 	Race    bool
     88 	Verbose bool
     89 
     90 	TrimPath string
     91 
     92 	TraceFunc func(name string) func()
     93 
     94 	pkgs  []string
     95 	paths map[string]string
     96 }
     97 
     98 func (c *Config) Map(pkgPrefix, pathPrefix string) error {
     99 	if c.paths == nil {
    100 		c.paths = make(map[string]string)
    101 	}
    102 	if _, ok := c.paths[pkgPrefix]; ok {
    103 		return fmt.Errorf("Duplicate package prefix: %q", pkgPrefix)
    104 	}
    105 
    106 	c.pkgs = append(c.pkgs, pkgPrefix)
    107 	c.paths[pkgPrefix] = pathPrefix
    108 
    109 	return nil
    110 }
    111 
    112 // Path takes a package name, applies the path mappings and returns the resulting path.
    113 //
    114 // If the package isn't mapped, we'll return false to prevent compilation attempts.
    115 func (c *Config) Path(pkg string) (string, bool, error) {
    116 	if c == nil || c.paths == nil {
    117 		return "", false, fmt.Errorf("No package mappings")
    118 	}
    119 
    120 	for _, pkgPrefix := range c.pkgs {
    121 		if pkg == pkgPrefix {
    122 			return c.paths[pkgPrefix], true, nil
    123 		} else if strings.HasPrefix(pkg, pkgPrefix+"/") {
    124 			return filepath.Join(c.paths[pkgPrefix], strings.TrimPrefix(pkg, pkgPrefix+"/")), true, nil
    125 		}
    126 	}
    127 
    128 	return "", false, nil
    129 }
    130 
    131 func (c *Config) trace(format string, a ...interface{}) func() {
    132 	if c.TraceFunc == nil {
    133 		return func() {}
    134 	}
    135 	s := strings.TrimSpace(fmt.Sprintf(format, a...))
    136 	return c.TraceFunc(s)
    137 }
    138 
    139 func un(f func()) {
    140 	f()
    141 }
    142 
    143 type GoPackage struct {
    144 	Name string
    145 
    146 	// Inputs
    147 	directDeps []*GoPackage // specified directly by the module
    148 	allDeps    []*GoPackage // direct dependencies and transitive dependencies
    149 	files      []string
    150 
    151 	// Outputs
    152 	pkgDir     string
    153 	output     string
    154 	hashResult []byte
    155 
    156 	// Status
    157 	mutex    sync.Mutex
    158 	compiled bool
    159 	failed   error
    160 	rebuilt  bool
    161 }
    162 
    163 // LinkedHashMap<string, GoPackage>
    164 type linkedDepSet struct {
    165 	packageSet  map[string](*GoPackage)
    166 	packageList []*GoPackage
    167 }
    168 
    169 func newDepSet() *linkedDepSet {
    170 	return &linkedDepSet{packageSet: make(map[string]*GoPackage)}
    171 }
    172 func (s *linkedDepSet) tryGetByName(name string) (*GoPackage, bool) {
    173 	pkg, contained := s.packageSet[name]
    174 	return pkg, contained
    175 }
    176 func (s *linkedDepSet) getByName(name string) *GoPackage {
    177 	pkg, _ := s.tryGetByName(name)
    178 	return pkg
    179 }
    180 func (s *linkedDepSet) add(name string, goPackage *GoPackage) {
    181 	s.packageSet[name] = goPackage
    182 	s.packageList = append(s.packageList, goPackage)
    183 }
    184 func (s *linkedDepSet) ignore(name string) {
    185 	s.packageSet[name] = nil
    186 }
    187 
    188 // FindDeps searches all applicable go files in `path`, parses all of them
    189 // for import dependencies that exist in pkgMap, then recursively does the
    190 // same for all of those dependencies.
    191 func (p *GoPackage) FindDeps(config *Config, path string) error {
    192 	defer un(config.trace("findDeps"))
    193 
    194 	depSet := newDepSet()
    195 	err := p.findDeps(config, path, depSet)
    196 	if err != nil {
    197 		return err
    198 	}
    199 	p.allDeps = depSet.packageList
    200 	return nil
    201 }
    202 
    203 // Roughly equivalent to go/build.Context.match
    204 func matchBuildTag(name string) bool {
    205 	if name == "" {
    206 		return false
    207 	}
    208 	if i := strings.Index(name, ","); i >= 0 {
    209 		ok1 := matchBuildTag(name[:i])
    210 		ok2 := matchBuildTag(name[i+1:])
    211 		return ok1 && ok2
    212 	}
    213 	if strings.HasPrefix(name, "!!") {
    214 		return false
    215 	}
    216 	if strings.HasPrefix(name, "!") {
    217 		return len(name) > 1 && !matchBuildTag(name[1:])
    218 	}
    219 
    220 	if name == runtime.GOOS || name == runtime.GOARCH || name == "gc" {
    221 		return true
    222 	}
    223 	for _, tag := range build.Default.BuildTags {
    224 		if tag == name {
    225 			return true
    226 		}
    227 	}
    228 	for _, tag := range build.Default.ReleaseTags {
    229 		if tag == name {
    230 			return true
    231 		}
    232 	}
    233 
    234 	return false
    235 }
    236 
    237 func parseBuildComment(comment string) (matches, ok bool) {
    238 	if !strings.HasPrefix(comment, "//") {
    239 		return false, false
    240 	}
    241 	for i, c := range comment {
    242 		if i < 2 || c == ' ' || c == '\t' {
    243 			continue
    244 		} else if c == '+' {
    245 			f := strings.Fields(comment[i:])
    246 			if f[0] == "+build" {
    247 				matches = false
    248 				for _, tok := range f[1:] {
    249 					matches = matches || matchBuildTag(tok)
    250 				}
    251 				return matches, true
    252 			}
    253 		}
    254 		break
    255 	}
    256 	return false, false
    257 }
    258 
    259 // findDeps is the recursive version of FindDeps. allPackages is the map of
    260 // all locally defined packages so that the same dependency of two different
    261 // packages is only resolved once.
    262 func (p *GoPackage) findDeps(config *Config, path string, allPackages *linkedDepSet) error {
    263 	// If this ever becomes too slow, we can look at reading the files once instead of twice
    264 	// But that just complicates things today, and we're already really fast.
    265 	foundPkgs, err := parser.ParseDir(token.NewFileSet(), path, func(fi os.FileInfo) bool {
    266 		name := fi.Name()
    267 		if fi.IsDir() || strings.HasSuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' {
    268 			return false
    269 		}
    270 		if runtime.GOOS != "darwin" && strings.HasSuffix(name, "_darwin.go") {
    271 			return false
    272 		}
    273 		if runtime.GOOS != "linux" && strings.HasSuffix(name, "_linux.go") {
    274 			return false
    275 		}
    276 		return true
    277 	}, parser.ImportsOnly|parser.ParseComments)
    278 	if err != nil {
    279 		return fmt.Errorf("Error parsing directory %q: %v", path, err)
    280 	}
    281 
    282 	var foundPkg *ast.Package
    283 	// foundPkgs is a map[string]*ast.Package, but we only want one package
    284 	if len(foundPkgs) != 1 {
    285 		return fmt.Errorf("Expected one package in %q, got %d", path, len(foundPkgs))
    286 	}
    287 	// Extract the first (and only) entry from the map.
    288 	for _, pkg := range foundPkgs {
    289 		foundPkg = pkg
    290 	}
    291 
    292 	var deps []string
    293 	localDeps := make(map[string]bool)
    294 
    295 	for filename, astFile := range foundPkg.Files {
    296 		ignore := false
    297 		for _, commentGroup := range astFile.Comments {
    298 			for _, comment := range commentGroup.List {
    299 				if matches, ok := parseBuildComment(comment.Text); ok && !matches {
    300 					ignore = true
    301 				}
    302 			}
    303 		}
    304 		if ignore {
    305 			continue
    306 		}
    307 
    308 		p.files = append(p.files, filename)
    309 
    310 		for _, importSpec := range astFile.Imports {
    311 			name, err := strconv.Unquote(importSpec.Path.Value)
    312 			if err != nil {
    313 				return fmt.Errorf("%s: invalid quoted string: <%s> %v", filename, importSpec.Path.Value, err)
    314 			}
    315 
    316 			if pkg, ok := allPackages.tryGetByName(name); ok {
    317 				if pkg != nil {
    318 					if _, ok := localDeps[name]; !ok {
    319 						deps = append(deps, name)
    320 						localDeps[name] = true
    321 					}
    322 				}
    323 				continue
    324 			}
    325 
    326 			var pkgPath string
    327 			if path, ok, err := config.Path(name); err != nil {
    328 				return err
    329 			} else if !ok {
    330 				// Probably in the stdlib, but if not, then the compiler will fail with a reasonable error message
    331 				// Mark it as such so that we don't try to decode its path again.
    332 				allPackages.ignore(name)
    333 				continue
    334 			} else {
    335 				pkgPath = path
    336 			}
    337 
    338 			pkg := &GoPackage{
    339 				Name: name,
    340 			}
    341 			deps = append(deps, name)
    342 			allPackages.add(name, pkg)
    343 			localDeps[name] = true
    344 
    345 			if err := pkg.findDeps(config, pkgPath, allPackages); err != nil {
    346 				return err
    347 			}
    348 		}
    349 	}
    350 
    351 	sort.Strings(p.files)
    352 
    353 	if config.Verbose {
    354 		fmt.Fprintf(os.Stderr, "Package %q depends on %v\n", p.Name, deps)
    355 	}
    356 
    357 	sort.Strings(deps)
    358 	for _, dep := range deps {
    359 		p.directDeps = append(p.directDeps, allPackages.getByName(dep))
    360 	}
    361 
    362 	return nil
    363 }
    364 
    365 func (p *GoPackage) Compile(config *Config, outDir string) error {
    366 	p.mutex.Lock()
    367 	defer p.mutex.Unlock()
    368 	if p.compiled {
    369 		return p.failed
    370 	}
    371 	p.compiled = true
    372 
    373 	// Build all dependencies in parallel, then fail if any of them failed.
    374 	var wg sync.WaitGroup
    375 	for _, dep := range p.directDeps {
    376 		wg.Add(1)
    377 		go func(dep *GoPackage) {
    378 			defer wg.Done()
    379 			dep.Compile(config, outDir)
    380 		}(dep)
    381 	}
    382 	wg.Wait()
    383 	for _, dep := range p.directDeps {
    384 		if dep.failed != nil {
    385 			p.failed = dep.failed
    386 			return p.failed
    387 		}
    388 	}
    389 
    390 	endTrace := config.trace("check compile %s", p.Name)
    391 
    392 	p.pkgDir = filepath.Join(outDir, strings.Replace(p.Name, "/", "-", -1))
    393 	p.output = filepath.Join(p.pkgDir, p.Name) + ".a"
    394 	shaFile := p.output + ".hash"
    395 
    396 	hash := sha1.New()
    397 	fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)
    398 
    399 	cmd := exec.Command(filepath.Join(goToolDir, "compile"),
    400 		"-o", p.output,
    401 		"-p", p.Name,
    402 		"-complete", "-pack", "-nolocalimports")
    403 	if !isGo18 {
    404 		cmd.Args = append(cmd.Args, "-c", fmt.Sprintf("%d", runtime.NumCPU()))
    405 	}
    406 	if config.Race {
    407 		cmd.Args = append(cmd.Args, "-race")
    408 		fmt.Fprintln(hash, "-race")
    409 	}
    410 	if config.TrimPath != "" {
    411 		cmd.Args = append(cmd.Args, "-trimpath", config.TrimPath)
    412 		fmt.Fprintln(hash, config.TrimPath)
    413 	}
    414 	for _, dep := range p.directDeps {
    415 		cmd.Args = append(cmd.Args, "-I", dep.pkgDir)
    416 		hash.Write(dep.hashResult)
    417 	}
    418 	for _, filename := range p.files {
    419 		cmd.Args = append(cmd.Args, filename)
    420 		fmt.Fprintln(hash, filename)
    421 
    422 		// Hash the contents of the input files
    423 		f, err := os.Open(filename)
    424 		if err != nil {
    425 			f.Close()
    426 			err = fmt.Errorf("%s: %v", filename, err)
    427 			p.failed = err
    428 			return err
    429 		}
    430 		_, err = io.Copy(hash, f)
    431 		if err != nil {
    432 			f.Close()
    433 			err = fmt.Errorf("%s: %v", filename, err)
    434 			p.failed = err
    435 			return err
    436 		}
    437 		f.Close()
    438 	}
    439 	p.hashResult = hash.Sum(nil)
    440 
    441 	var rebuild bool
    442 	if _, err := os.Stat(p.output); err != nil {
    443 		rebuild = true
    444 	}
    445 	if !rebuild {
    446 		if oldSha, err := ioutil.ReadFile(shaFile); err == nil {
    447 			rebuild = !bytes.Equal(oldSha, p.hashResult)
    448 		} else {
    449 			rebuild = true
    450 		}
    451 	}
    452 
    453 	endTrace()
    454 	if !rebuild {
    455 		return nil
    456 	}
    457 	defer un(config.trace("compile %s", p.Name))
    458 
    459 	err := os.RemoveAll(p.pkgDir)
    460 	if err != nil {
    461 		err = fmt.Errorf("%s: %v", p.Name, err)
    462 		p.failed = err
    463 		return err
    464 	}
    465 
    466 	err = os.MkdirAll(filepath.Dir(p.output), 0777)
    467 	if err != nil {
    468 		err = fmt.Errorf("%s: %v", p.Name, err)
    469 		p.failed = err
    470 		return err
    471 	}
    472 
    473 	cmd.Stdin = nil
    474 	cmd.Stdout = os.Stdout
    475 	cmd.Stderr = os.Stderr
    476 	if config.Verbose {
    477 		fmt.Fprintln(os.Stderr, cmd.Args)
    478 	}
    479 	err = cmd.Run()
    480 	if err != nil {
    481 		commandText := strings.Join(cmd.Args, " ")
    482 		err = fmt.Errorf("%q: %v", commandText, err)
    483 		p.failed = err
    484 		return err
    485 	}
    486 
    487 	err = ioutil.WriteFile(shaFile, p.hashResult, 0666)
    488 	if err != nil {
    489 		err = fmt.Errorf("%s: %v", p.Name, err)
    490 		p.failed = err
    491 		return err
    492 	}
    493 
    494 	p.rebuilt = true
    495 
    496 	return nil
    497 }
    498 
    499 func (p *GoPackage) Link(config *Config, out string) error {
    500 	if p.Name != "main" {
    501 		return fmt.Errorf("Can only link main package")
    502 	}
    503 	endTrace := config.trace("check link %s", p.Name)
    504 
    505 	shaFile := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_hash")
    506 
    507 	if !p.rebuilt {
    508 		if _, err := os.Stat(out); err != nil {
    509 			p.rebuilt = true
    510 		} else if oldSha, err := ioutil.ReadFile(shaFile); err != nil {
    511 			p.rebuilt = true
    512 		} else {
    513 			p.rebuilt = !bytes.Equal(oldSha, p.hashResult)
    514 		}
    515 	}
    516 	endTrace()
    517 	if !p.rebuilt {
    518 		return nil
    519 	}
    520 	defer un(config.trace("link %s", p.Name))
    521 
    522 	err := os.Remove(shaFile)
    523 	if err != nil && !os.IsNotExist(err) {
    524 		return err
    525 	}
    526 	err = os.Remove(out)
    527 	if err != nil && !os.IsNotExist(err) {
    528 		return err
    529 	}
    530 
    531 	cmd := exec.Command(filepath.Join(goToolDir, "link"), "-o", out)
    532 	if config.Race {
    533 		cmd.Args = append(cmd.Args, "-race")
    534 	}
    535 	for _, dep := range p.allDeps {
    536 		cmd.Args = append(cmd.Args, "-L", dep.pkgDir)
    537 	}
    538 	cmd.Args = append(cmd.Args, p.output)
    539 	cmd.Stdin = nil
    540 	cmd.Stdout = os.Stdout
    541 	cmd.Stderr = os.Stderr
    542 	if config.Verbose {
    543 		fmt.Fprintln(os.Stderr, cmd.Args)
    544 	}
    545 	err = cmd.Run()
    546 	if err != nil {
    547 		return fmt.Errorf("command %s failed with error %v", cmd.Args, err)
    548 	}
    549 
    550 	return ioutil.WriteFile(shaFile, p.hashResult, 0666)
    551 }
    552 
    553 func Build(config *Config, out, pkg string) (*GoPackage, error) {
    554 	p := &GoPackage{
    555 		Name: "main",
    556 	}
    557 
    558 	lockFileName := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+".lock")
    559 	lockFile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0666)
    560 	if err != nil {
    561 		return nil, fmt.Errorf("Error creating lock file (%q): %v", lockFileName, err)
    562 	}
    563 	defer lockFile.Close()
    564 
    565 	err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX)
    566 	if err != nil {
    567 		return nil, fmt.Errorf("Error locking file (%q): %v", lockFileName, err)
    568 	}
    569 
    570 	path, ok, err := config.Path(pkg)
    571 	if err != nil {
    572 		return nil, fmt.Errorf("Error finding package %q for main: %v", pkg, err)
    573 	}
    574 	if !ok {
    575 		return nil, fmt.Errorf("Could not find package %q", pkg)
    576 	}
    577 
    578 	intermediates := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_intermediates")
    579 	if err := os.MkdirAll(intermediates, 0777); err != nil {
    580 		return nil, fmt.Errorf("Failed to create intermediates directory: %v", err)
    581 	}
    582 
    583 	if err := p.FindDeps(config, path); err != nil {
    584 		return nil, fmt.Errorf("Failed to find deps of %v: %v", pkg, err)
    585 	}
    586 	if err := p.Compile(config, intermediates); err != nil {
    587 		return nil, fmt.Errorf("Failed to compile %v: %v", pkg, err)
    588 	}
    589 	if err := p.Link(config, out); err != nil {
    590 		return nil, fmt.Errorf("Failed to link %v: %v", pkg, err)
    591 	}
    592 	return p, nil
    593 }
    594 
    595 // rebuildMicrofactory checks to see if microfactory itself needs to be rebuilt,
    596 // and if does, it will launch a new copy and return true. Otherwise it will return
    597 // false to continue executing.
    598 func rebuildMicrofactory(config *Config, mybin string) bool {
    599 	if pkg, err := Build(config, mybin, "github.com/google/blueprint/microfactory/main"); err != nil {
    600 		fmt.Fprintln(os.Stderr, err)
    601 		os.Exit(1)
    602 	} else if !pkg.rebuilt {
    603 		return false
    604 	}
    605 
    606 	cmd := exec.Command(mybin, os.Args[1:]...)
    607 	cmd.Stdin = os.Stdin
    608 	cmd.Stdout = os.Stdout
    609 	cmd.Stderr = os.Stderr
    610 	if err := cmd.Run(); err == nil {
    611 		return true
    612 	} else if e, ok := err.(*exec.ExitError); ok {
    613 		os.Exit(e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
    614 	}
    615 	os.Exit(1)
    616 	return true
    617 }
    618 
    619 // microfactory.bash will make a copy of this file renamed into the main package for use with `go run`
    620 func main() { Main() }
    621 func Main() {
    622 	var output, mybin string
    623 	var config Config
    624 	pkgMap := pkgPathMappingVar{&config}
    625 
    626 	flags := flag.NewFlagSet("", flag.ExitOnError)
    627 	flags.BoolVar(&config.Race, "race", false, "enable data race detection.")
    628 	flags.BoolVar(&config.Verbose, "v", false, "Verbose")
    629 	flags.StringVar(&output, "o", "", "Output file")
    630 	flags.StringVar(&mybin, "b", "", "Microfactory binary location")
    631 	flags.StringVar(&config.TrimPath, "trimpath", "", "remove prefix from recorded source file paths")
    632 	flags.Var(&pkgMap, "pkg-path", "Mapping of package prefixes to file paths")
    633 	err := flags.Parse(os.Args[1:])
    634 
    635 	if err == flag.ErrHelp || flags.NArg() != 1 || output == "" {
    636 		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "-o out/binary <main-package>")
    637 		flags.PrintDefaults()
    638 		os.Exit(1)
    639 	}
    640 
    641 	tracePath := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+".trace")
    642 	if traceFile, err := os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666); err == nil {
    643 		defer traceFile.Close()
    644 		config.TraceFunc = func(name string) func() {
    645 			fmt.Fprintf(traceFile, "%d B %s\n", time.Now().UnixNano()/1000, name)
    646 			return func() {
    647 				fmt.Fprintf(traceFile, "%d E %s\n", time.Now().UnixNano()/1000, name)
    648 			}
    649 		}
    650 	}
    651 	if executable, err := os.Executable(); err == nil {
    652 		defer un(config.trace("microfactory %s", executable))
    653 	} else {
    654 		defer un(config.trace("microfactory <unknown>"))
    655 	}
    656 
    657 	if mybin != "" {
    658 		if rebuildMicrofactory(&config, mybin) {
    659 			return
    660 		}
    661 	}
    662 
    663 	if _, err := Build(&config, output, flags.Arg(0)); err != nil {
    664 		fmt.Fprintln(os.Stderr, err)
    665 		os.Exit(1)
    666 	}
    667 }
    668 
    669 // pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
    670 // <package-prefix>=<path-prefix> mappings.
    671 type pkgPathMappingVar struct{ *Config }
    672 
    673 func (pkgPathMappingVar) String() string {
    674 	return "<package-prefix>=<path-prefix>"
    675 }
    676 
    677 func (p *pkgPathMappingVar) Set(value string) error {
    678 	equalPos := strings.Index(value, "=")
    679 	if equalPos == -1 {
    680 		return fmt.Errorf("Argument must be in the form of: %q", p.String())
    681 	}
    682 
    683 	pkgPrefix := strings.TrimSuffix(value[:equalPos], "/")
    684 	pathPrefix := strings.TrimSuffix(value[equalPos+1:], "/")
    685 
    686 	return p.Map(pkgPrefix, pathPrefix)
    687 }
    688