copy project to temp location

This commit is contained in:
lyyyuna 2021-05-23 22:23:35 +08:00
parent 922340d8c4
commit 18c4c4a2da
10 changed files with 339 additions and 19 deletions

View File

@ -1,14 +1,13 @@
package cmd
import (
"github.com/qiniu/goc/v2/pkg/flag"
"github.com/qiniu/goc/v2/pkg/build"
"github.com/spf13/cobra"
)
var buildCmd = &cobra.Command{
Use: "build",
Run: build,
Run: buildAction,
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
}
@ -17,8 +16,7 @@ func init() {
rootCmd.AddCommand(buildCmd)
}
func build(cmd *cobra.Command, args []string) {
remainedArgs := flag.BuildCmdArgsParse(cmd, args)
where, buildName := flag.GetPackagesDir(remainedArgs)
func buildAction(cmd *cobra.Command, args []string) {
b := build.NewBuild(cmd, args)
b.Build()
}

7
go.mod
View File

@ -3,19 +3,14 @@ module github.com/qiniu/goc/v2
go 1.16
require (
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1
github.com/ugorji/go v1.1.4 // indirect
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
github.com/tongjingran/copy v1.4.2 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
k8s.io/kubectl v0.20.5 // indirect
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc // indirect
)

6
go.sum
View File

@ -299,6 +299,10 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -376,6 +380,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tongjingran/copy v1.4.2 h1:faPaod07yG6Z+o1B52Vu1KTvRb8il5VDNKLprC1BmsE=
github.com/tongjingran/copy v1.4.2/go.mod h1:Njma1OR5OuzB8pLAmQSzonHXzba+DDiPVmMSonpSpy4=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=

36
pkg/build/build.go Normal file
View File

@ -0,0 +1,36 @@
package build
import (
"github.com/qiniu/goc/v2/pkg/flag"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/spf13/cobra"
)
// Build struct a build
// most configurations are stored in global variables: config.GocConfig & config.GoConfig
type Build struct {
}
// NewBuild creates a Build struct
//
// consumes args, get package dirs, read project meta info.
func NewBuild(cmd *cobra.Command, args []string) *Build {
b := &Build{}
remainedArgs := flag.BuildCmdArgsParse(cmd, args)
flag.GetPackagesDir(remainedArgs)
b.readProjectMetaInfo()
b.displayProjectMetaInfo()
return b
}
// Build starts go build
//
// 1. copy project to temp,
// 2. inject cover variables and functions into the project,
// 3. build the project in temp.
func (b *Build) Build() {
b.copyProjectToTmp()
defer b.clean()
log.Donef("project copied to temporary directory")
}

68
pkg/build/goenv.go Normal file
View File

@ -0,0 +1,68 @@
package build
import (
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/cover"
"github.com/qiniu/goc/v2/pkg/log"
)
// readProjectMetaInfo reads all meta informations of the corresponding project
func (b *Build) readProjectMetaInfo() {
// get gopath & gobin
config.GocConfig.GOPATH = b.readGOPATH()
config.GocConfig.GOBIN = b.readGOBIN()
// 获取当前目录及其依赖的 package list
config.GocConfig.Pkgs = cover.ListPackages(config.GocConfig.CurPkgDir)
// get mod info
pkgs := config.GocConfig.Pkgs
for _, pkg := range pkgs {
// check if go modules is enabled
if pkg.Module == nil {
log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on")
}
// 工程根目录
config.GocConfig.CurModProjectDir = pkg.Root
break
}
// get tmp folder name
config.GocConfig.TmpModProjectDir = filepath.Join(os.TempDir(), tmpFolderName(config.GocConfig.CurModProjectDir))
// get cur pkg dir in the corresponding tmp dir
config.GocConfig.TmpPkgDir = filepath.Join(config.GocConfig.TmpModProjectDir, config.GocConfig.CurPkgDir[len(config.GocConfig.CurModProjectDir):])
log.Donef("project meta information parsed")
}
// displayProjectMetaInfo prints basic infomation of this project to stdout
func (b *Build) displayProjectMetaInfo() {
log.Infof("Project Infomation")
log.Infof("GOPATH: %v", config.GocConfig.GOPATH)
log.Infof("GOBIN: %v", config.GocConfig.GOBIN)
log.Infof("Project Directory: %v", config.GocConfig.CurModProjectDir)
log.Infof("Temporary Project Directory: %v", config.GocConfig.TmpModProjectDir)
log.Infof("")
}
// readGOPATH reads GOPATH use go env GOPATH command
func (b *Build) readGOPATH() string {
out, err := exec.Command("go", "env", "GOPATH").Output()
if err != nil {
log.Fatalf("fail to read GOPATH: %v", err)
}
return strings.TrimSpace(string(out))
}
// readGOBIN reads GOBIN use go env GOBIN command
func (b *Build) readGOBIN() string {
out, err := exec.Command("go", "env", "GOBIN").Output()
if err != nil {
log.Fatalf("fail to read GOBIN: %v", err)
}
return strings.TrimSpace(string(out))
}

75
pkg/build/tmpfolder.go Normal file
View File

@ -0,0 +1,75 @@
package build
import (
"crypto/sha256"
"fmt"
"os"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/tongjingran/copy"
)
// copyProjectToTmp copies project files to the temporary directory
//
// It will ignore .git and irregular files, only copy source(text) files
func (b *Build) copyProjectToTmp() {
curProject := config.GocConfig.CurModProjectDir
tmpProject := config.GocConfig.TmpModProjectDir
if _, err := os.Stat(tmpProject); !os.IsNotExist(err) {
log.Infof("find previous temporary directory, delete")
err := os.RemoveAll(tmpProject)
if err != nil {
log.Fatalf("fail to remove preivous temporary directory: %v", err)
}
}
log.StartWait("coping project")
err := os.MkdirAll(tmpProject, os.ModePerm)
if err != nil {
log.Fatalf("fail to create temporary directory: %v", err)
}
// copy
if err := copy.Copy(curProject, tmpProject, copy.Options{Skip: skipCopy}); err != nil {
log.Fatalf("fail to copy the folder from %v to %v, the err: %v", curProject, tmpProject, err)
}
log.StopWait()
}
// tmpFolderName generates a directory name according to the path
func tmpFolderName(path string) string {
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
return "goc-build-" + h
}
// skipCopy skip copy .git dir and irregular files
func skipCopy(src string, info os.FileInfo) (bool, error) {
irregularModeType := os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
if strings.HasSuffix(src, "/.git") {
log.Debugf("skip .git dir [%s]", src)
return true, nil
}
if info.Mode()&irregularModeType != 0 {
log.Debugf("skip file [%s], the file mode is [%s]", src, info.Mode().String())
return true, nil
}
return false, nil
}
// clean clears the temporary project
func (b *Build) clean() {
if config.GocConfig.Debug != true {
if err := os.RemoveAll(config.GocConfig.TmpModProjectDir); err != nil {
log.Fatalf("fail to delete the temporary project: %v", config.GocConfig.TmpModProjectDir)
}
log.Donef("delete the temporary project")
} else {
log.Debugf("--debug is enabled, keep the temporary project")
}
}

View File

@ -1,5 +1,7 @@
package config
import "time"
type gocConfig struct {
Debug bool
CurPkgDir string
@ -7,8 +9,13 @@ type gocConfig struct {
TmpModProjectDir string
TmpPkgDir string
BinaryName string
Pkgs map[string]*Package
GOPATH string
GOBIN string
IsMod bool // deprecated
}
// GocConfig 全局变量,存放 goc 的各种元属性
var GocConfig gocConfig
type goConfig struct {
@ -48,3 +55,72 @@ type goConfig struct {
}
var GoConfig goConfig
// PackageCover holds all the generate coverage variables of a package
type PackageCover struct {
Package *Package
Vars map[string]*FileVar
}
// FileVar holds the name of the generated coverage variables targeting the named file.
type FileVar struct {
File string
Var string
}
// Package map a package output by go list
// this is subset of package struct in: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L58
type Package struct {
Dir string `json:"Dir"` // directory containing package sources
ImportPath string `json:"ImportPath"` // import path of package in dir
Name string `json:"Name"` // package name
Target string `json:",omitempty"` // installed target for this package (may be executable)
Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package
Module *ModulePublic `json:",omitempty"` // info about package's module, if any
Goroot bool `json:"Goroot,omitempty"` // is this package in the Go root?
Standard bool `json:"Standard,omitempty"` // is this package part of the standard Go library?
DepOnly bool `json:"DepOnly,omitempty"` // package is only a dependency, not explicitly listed
// Source files
GoFiles []string `json:"GoFiles,omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string `json:"CgoFiles,omitempty"` // .go source files that import "C"
// Dependency information
Deps []string `json:"Deps,omitempty"` // all (recursively) imported dependencies
Imports []string `json:",omitempty"` // import paths used by this package
ImportMap map[string]string `json:",omitempty"` // map from source import to ImportPath (identity entries omitted)
// Error information
Incomplete bool `json:"Incomplete,omitempty"` // this package or a dependency has an error
Error *PackageError `json:"Error,omitempty"` // error loading package
DepsErrors []*PackageError `json:"DepsErrors,omitempty"` // errors loading dependencies
}
// ModulePublic represents the package info of a module
type ModulePublic struct {
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
GoVersion string `json:",omitempty"` // go version used in module
Error *ModuleError `json:",omitempty"` // error loading module
}
// ModuleError represents the error loading module
type ModuleError struct {
Err string // error text
}
// PackageError is the error info for a package when list failed
type PackageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error (if present, file:line:col)
Err string // the error itself
}

56
pkg/cover/list.go Normal file
View File

@ -0,0 +1,56 @@
package cover
import (
"bytes"
"encoding/json"
"errors"
"io"
"os/exec"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
)
var (
// ErrCoverPkgFailed represents the error that fails to inject the package
ErrCoverPkgFailed = errors.New("fail to inject code to project")
// ErrCoverListFailed represents the error that fails to list package dependencies
ErrCoverListFailed = errors.New("fail to list package dependencies")
)
// ListPackages list all packages under specific via go list command.
func ListPackages(dir string) map[string]*config.Package {
cmd := exec.Command("go", "list", "-json", "./...")
cmd.Dir = dir
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
out, err := cmd.Output()
if err != nil {
log.Fatalf("execute go list -json failed, err: %v, stdout: %v, stderr: %v", err, string(out), errBuf.String())
}
// 有些时候 go 命令会打印一些信息到 stderr但其实命令整体是成功运行了
if errBuf.String() != "" {
log.Errorf("%v", errBuf.String())
}
dec := json.NewDecoder(bytes.NewBuffer(out))
pkgs := make(map[string]*config.Package, 0)
for {
var pkg config.Package
if err := dec.Decode(&pkg); err != nil {
if err == io.EOF {
break
}
log.Fatalf("reading go list output error: %v", err)
}
if pkg.Error != nil {
log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
}
pkgs[pkg.ImportPath] = &pkg
}
return pkgs
}

View File

@ -16,7 +16,8 @@ However, other flags' order are same with the go official command.
`
// BuildCmdArgsParse parse both go flags and goc flags, it returns all non-flag arguments.
// It will log fatal if error
//
// 吞下 [packages] 之前所有的 flags.
func BuildCmdArgsParse(cmd *cobra.Command, args []string) []string {
// 首先解析 cobra 定义的 flag
allFlagSets := cmd.Flags()

View File

@ -6,17 +6,19 @@ import (
"os"
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
)
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
//
// Return 1 [packages] 所在的目录位置,供后续插桩使用。
// 函数获取 1 [packages] 所在的目录位置,供后续插桩使用。
//
// Return 2 如果参数是 *.go第一个 .go 文件的文件名。go build 中,二进制名字既可能是目录名也可能是文件名,和参数类型有关。
// 函数获取 2 如果参数是 *.go第一个 .go 文件的文件名。go build 中,二进制名字既可能是目录名也可能是文件名,和参数类型有关。
//
// 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误
// 这里只考虑 go mod 的方式
func GetPackagesDir(patterns []string) (string, string) {
func GetPackagesDir(patterns []string) {
for _, p := range patterns {
// patterns 只支持两种格式
// 1. 要么是直接指向某些 .go 文件的相对/绝对路径
@ -32,7 +34,12 @@ func GetPackagesDir(patterns []string) (string, string) {
if err != nil {
log.Fatalf("%v", err)
}
return filepath.Dir(absp), filepath.Base(absp)
// 获取当前 [packages] 所在的目录位置,供后续插桩使用。
config.GocConfig.CurPkgDir = filepath.Dir(absp)
// 获取二进制名字
config.GocConfig.BinaryName = filepath.Base(absp)
return
}
}
}
@ -43,7 +50,9 @@ func GetPackagesDir(patterns []string) (string, string) {
log.Fatalf("%v", err)
}
return coverWd, ""
config.GocConfig.CurPkgDir = coverWd
config.GocConfig.BinaryName = ""
return
}
// goFilesPackage 对一组 go 文件解析,判断是否合法