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