github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/tools/depsgen/gocmd.go (about)

     1  // Copyright 2015 The rkt Authors
     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  package main
    16  
    17  import (
    18  	"bytes"
    19  	"os/exec"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"github.com/coreos/rkt/tools/common"
    25  )
    26  
    27  const (
    28  	// goSeparator is used to separate tuple elements in go list
    29  	// format string.
    30  	goSeparator = "!_##_!"
    31  	// goMakeFunction is a template for generating all files for a
    32  	// given module in a given repo.
    33  	goMakeFunction = "$(shell $(GO_ENV) \"$(DEPSGENTOOL)\" go --repo \"!!!REPO!!!\" --module \"!!!MODULE!!!\" --mode files)"
    34  	goCmd          = "go"
    35  )
    36  
    37  type goDepsMode int
    38  
    39  const (
    40  	goMakeMode goDepsMode = iota
    41  	goFilesMode
    42  )
    43  
    44  func init() {
    45  	cmds[goCmd] = goDeps
    46  }
    47  
    48  func goDeps(args []string) string {
    49  	target, repo, module, mode := goGetArgs(args)
    50  	deps := goGetPackageDeps(repo, module)
    51  	switch mode {
    52  	case goMakeMode:
    53  		return GenerateFileDeps(target, goGetMakeFunction(repo, module), deps)
    54  	case goFilesMode:
    55  		return strings.Join(deps, " ")
    56  	}
    57  	panic("Should not happen")
    58  }
    59  
    60  // goGetMakeFunction returns a make snippet which will call depsgen go
    61  // with "files" mode.
    62  func goGetMakeFunction(repo, module string) string {
    63  	return replacePlaceholders(goMakeFunction, "REPO", repo, "MODULE", module)
    64  }
    65  
    66  // getArgs parses given parameters and returns target, repo, module and
    67  // mode. If mode is "files", then target is optional.
    68  func goGetArgs(args []string) (string, string, string, goDepsMode) {
    69  	f, target := standardFlags(goCmd)
    70  	repo := f.String("repo", "", "Go repo (example: github.com/coreos/rkt)")
    71  	module := f.String("module", "", "Module inside Go repo (example: stage1)")
    72  	mode := f.String("mode", "make", "Mode to use (make - print deps as makefile [default], files - print a list of files)")
    73  
    74  	f.Parse(args)
    75  	if *repo == "" {
    76  		common.Die("--repo parameter must be specified and cannot be empty")
    77  	}
    78  	if *module == "" {
    79  		common.Die("--module parameter must be specified and cannot be empty")
    80  	}
    81  
    82  	var dMode goDepsMode
    83  
    84  	switch *mode {
    85  	case "make":
    86  		dMode = goMakeMode
    87  		if *target == "" {
    88  			common.Die("--target parameter must be specified and cannot be empty when using 'make' mode")
    89  		}
    90  	case "files":
    91  		dMode = goFilesMode
    92  	default:
    93  		common.Die("unknown --mode parameter %q - expected either 'make' or 'files'", *mode)
    94  	}
    95  	return *target, *repo, *module, dMode
    96  }
    97  
    98  // goGetPackageDeps returns a list of files that are used to build a
    99  // module in a given repo.
   100  func goGetPackageDeps(repo, module string) []string {
   101  	pkg := path.Join(repo, module)
   102  	deps := []string{pkg}
   103  	for _, d := range goGetDeps(pkg) {
   104  		if strings.HasPrefix(d, repo) {
   105  			deps = append(deps, d)
   106  		}
   107  	}
   108  	return goGetFiles(repo, deps)
   109  }
   110  
   111  // goGetDeps gets all dependencies, direct or indirect, of a given
   112  // package.
   113  func goGetDeps(pkg string) []string {
   114  	rawDeps := goRun(goList([]string{"Deps"}, []string{pkg}))
   115  	// we expect only one line
   116  	if len(rawDeps) != 1 {
   117  		return []string{}
   118  	}
   119  	return goSliceRawSlice(rawDeps[0])
   120  }
   121  
   122  // goGetFiles returns a list of files that are in given packages. File
   123  // paths are "relative" to passed repo.
   124  func goGetFiles(repo string, pkgs []string) []string {
   125  	params := []string{
   126  		"ImportPath",
   127  		"GoFiles",
   128  		"CgoFiles",
   129  	}
   130  	var allFiles []string
   131  	rawTuples := goRun(goList(params, pkgs))
   132  	for _, raw := range rawTuples {
   133  		tuple := goSliceRawTuple(raw)
   134  		module := strings.TrimPrefix(tuple[0], repo+"/")
   135  		files := append(goSliceRawSlice(tuple[1]), goSliceRawSlice(tuple[2])...)
   136  		for i := 0; i < len(files); i++ {
   137  			files[i] = filepath.Join(module, files[i])
   138  		}
   139  		allFiles = append(allFiles, files...)
   140  	}
   141  	return allFiles
   142  }
   143  
   144  // goList returns an array of strings describing go list invocation
   145  // with format string consisting all given params separated with
   146  // !_##_! for all given packages.
   147  func goList(params, pkgs []string) []string {
   148  	templateParams := make([]string, 0, len(params))
   149  	for _, p := range params {
   150  		templateParams = append(templateParams, "{{."+p+"}}")
   151  	}
   152  	return append([]string{
   153  		"go",
   154  		"list",
   155  		"-f", strings.Join(templateParams, goSeparator),
   156  	}, pkgs...)
   157  }
   158  
   159  // goRun executes given argument list and captures its output. The
   160  // output is sliced into lines with empty lines being discarded.
   161  func goRun(argv []string) []string {
   162  	cmd := exec.Command(argv[0], argv[1:]...)
   163  	stdout := new(bytes.Buffer)
   164  	stderr := new(bytes.Buffer)
   165  	cmd.Stdout = stdout
   166  	cmd.Stderr = stderr
   167  	if err := cmd.Run(); err != nil {
   168  		common.Die("Error running %s: %v: %s", strings.Join(argv, " "), err, stderr.String())
   169  	}
   170  	rawLines := strings.Split(stdout.String(), "\n")
   171  	lines := make([]string, 0, len(rawLines))
   172  	for _, line := range rawLines {
   173  		if trimmed := strings.TrimSpace(line); trimmed != "" {
   174  			lines = append(lines, trimmed)
   175  		}
   176  	}
   177  	return lines
   178  }
   179  
   180  // goSliceRawSlice slices given string representation of a slice into
   181  // slice of strings.
   182  func goSliceRawSlice(s string) []string {
   183  	s = strings.TrimPrefix(s, "[")
   184  	s = strings.TrimSuffix(s, "]")
   185  	s = strings.TrimSpace(s)
   186  	if s == "" {
   187  		return nil
   188  	}
   189  	a := strings.Split(s, " ")
   190  	return a
   191  }
   192  
   193  // goSliceRawTuple slices given string along !_##_! goSeparator to slice
   194  // of strings. Returned slice might need another round of slicing with
   195  // goSliceRawSlice.
   196  func goSliceRawTuple(t string) []string {
   197  	return strings.Split(t, goSeparator)
   198  }