This commit is contained in:
lyyyuna 2020-06-16 13:21:28 +08:00
parent 103dafe41d
commit 57498dd11f
10 changed files with 180 additions and 80 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra"
"log"
)
var buildCmd = &cobra.Command{
@ -27,9 +28,7 @@ var buildCmd = &cobra.Command{
Short: "Do cover for all go files and execute go build command",
Long: `
First of all, this build command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target in this temporary directory, finally go build command will be executed and binaries generated to their original place.
To pass original go build flags to goc command, place them after "--", see examples below for reference.
`,
`,
Example: `
# Build the current binary with cover variables injected. The binary will be generated in the current folder.
goc build
@ -38,20 +37,26 @@ goc build
goc build --center=http://127.0.0.1:7777
# Build the current binary with cover variables injected, and redirect output to /to/this/path.
goc build -- -o /to/this/path
goc build --output /to/this/path
# Build the current binary with cover variables injected, and set necessary build flags: -ldflags "-extldflags -static" -tags="embed kodo".
goc build -- -ldflags "-extldflags -static" -tags="embed kodo"
goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
`,
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
gocBuild, err := build.NewBuild(buildFlags, packages, buildOutput)
if err != nil {
log.Fatalf("Fail to NewBuild: %v", err)
}
// remove temporary directory if needed
defer gocBuild.Clean()
// doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory
cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory
gocBuild.Build()
err = gocBuild.Build()
if err != nil {
log.Fatalf("Fail to build: %v", err)
}
return
},
}

View File

@ -19,6 +19,7 @@ package cmd
import (
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -27,8 +28,6 @@ var installCmd = &cobra.Command{
Short: "Do cover for all go files and execute go install command",
Long: `
First of all, this install command will copy the project code and its necessary dependencies to a temporary directory, then do cover for the target in this temporary directory, finally go install command will be executed and binaries generated to their original place.
To pass original go build flags to goc command, place them after "--", see examples below for reference.
`,
Example: `
# Install all binaries with cover variables injected. The binary will be installed in $GOPATH/bin or $HOME/go/bin if directory existed.
@ -41,14 +40,21 @@ goc install --center=http://127.0.0.1:7777
goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
`,
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewInstall(buildFlags, packages)
gocBuild, err := build.NewInstall(buildFlags, packages)
if err != nil {
log.Fatalf("Fail to NewInstall: %v", err)
}
// remove temporary directory if needed
defer gocBuild.Clean()
// doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory
cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory
gocBuild.Install()
err = gocBuild.Install()
if err != nil {
log.Fatalf("Fail to install: %v", err)
}
return
},
}

View File

@ -36,7 +36,7 @@ It is exactly behave as 'go run .' in addition of some internal goc features.`,
goc run .
`,
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
gocBuild, _ := build.NewBuild(buildFlags, packages, buildOutput)
gocBuild.GoRunExecFlag = goRunExecFlag
gocBuild.GoRunArguments = goRunArguments
defer gocBuild.Clean()

View File

@ -35,9 +35,11 @@ type Build struct {
TmpDir string // the temporary directory to build the project
TmpWorkingDir string // the working directory in the temporary directory, which is corresponding to the current directory in the project directory
IsMod bool // determine whether it is a Mod project
Root string // Project Root
Target string // the binary name that go build generate
Root string
// Project Root:
// 1. legacy, root == GOPATH
// 2. mod, root == go.mod Dir
Target string // the binary name that go build generate
// keep compatible with go commands:
// go run [build flags] [-exec xprog] package [arguments...]
// go build [-o output] [-i] [build flags] [packages]
@ -50,26 +52,33 @@ type Build struct {
// NewBuild creates a Build struct which can build from goc temporary directory,
// and generate binary in current working directory
func NewBuild(buildflags string, packages string, outputDir string) *Build {
func NewBuild(buildflags string, packages string, outputDir string) (*Build, error) {
// buildflags = buildflags + " -o " + outputDir
b := &Build{
BuildFlags: buildflags,
Packages: packages,
}
if false == b.validatePackageForBuild() {
log.Fatalln("packages only support \".\"")
log.Errorln(ErrWrongPackageTypeForBuild)
return nil, ErrWrongPackageTypeForBuild
}
b.MvProjectsToTmp()
b.Target = b.determineOutputDir(outputDir)
return b
dir, err := b.determineOutputDir(outputDir)
b.Target = dir
if err != nil {
return nil, err
}
return b, nil
}
func (b *Build) Build() {
func (b *Build) Build() error {
log.Infoln("Go building in temp...")
// new -o will overwrite previous ones
b.BuildFlags = b.BuildFlags + " -o " + b.Target
cmd := exec.Command("/bin/bash", "-c", "go build "+b.BuildFlags+" "+b.Packages)
cmd.Dir = b.TmpWorkingDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if b.NewGOPATH != "" {
// Change to temp GOPATH for go install command
@ -77,22 +86,30 @@ func (b *Build) Build() {
}
log.Printf("go build cmd is: %v", cmd.Args)
out, err := cmd.CombinedOutput()
err := cmd.Start()
if err != nil {
log.Fatalf("Fail to execute: %v. The error is: %v, the stdout/stderr is: %v", cmd.Args, err, string(out))
log.Errorf("Fail to execute: %v. The error is: %v", cmd.Args, err)
return fmt.Errorf("fail to execute: %v: %w", cmd.Args, err)
}
if err = cmd.Wait(); err != nil {
log.Errorf("go build failed. The error is: %v", err)
return fmt.Errorf("go build faileds: %w", err)
}
log.Println("Go build exit successful.")
return nil
}
// determineOutputDir, as we only allow . as package name,
// the binary name is always same as the directory name of current directory
func (b *Build) determineOutputDir(outputDir string) string {
func (b *Build) determineOutputDir(outputDir string) (string, error) {
if b.TmpDir == "" {
log.Fatalln("Can only be called after Build.MvProjectsToTmp().")
log.Errorf("Can only be called after Build.MvProjectsToTmp()", ErrWrongCallSequence)
return "", fmt.Errorf("can only be called after Build.MvProjectsToTmp(): %w", ErrWrongCallSequence)
}
curWorkingDir, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directory, the err: %v.", err)
log.Errorf("Cannot get current working directory: %v", err)
return "", fmt.Errorf("cannot get current working directory: %w", err)
}
if outputDir == "" {
@ -102,13 +119,14 @@ func (b *Build) determineOutputDir(outputDir string) string {
// replace "_" with "-" in the import path
last = strings.ReplaceAll(last, "_", "-")
}
return filepath.Join(curWorkingDir, last)
return filepath.Join(curWorkingDir, last), nil
}
abs, err := filepath.Abs(outputDir)
if err != nil {
log.Fatalf("Fail to transform the path: %v to absolute path, the error is: %v", outputDir, err)
log.Errorf("Fail to transform the path: %v to absolute path: %v", outputDir, err)
return "", fmt.Errorf("fail to transform the path %v to absolute path: %w", outputDir, err)
}
return abs
return abs, nil
}
// validatePackageForBuild only allow . as package name

14
pkg/build/errors.go Normal file
View File

@ -0,0 +1,14 @@
package build
import (
"errors"
)
var (
ErrShouldNotReached = errors.New("should never be reached")
ErrGocShouldExecInProject = errors.New("goc not executed in project directory")
ErrWrongPackageTypeForInstall = errors.New("packages only support \".\" and \"./...\"")
ErrWrongPackageTypeForBuild = errors.New("packages only support \".\"")
ErrWrongCallSequence = errors.New("function should be called in a specified sequence")
ErrNoplaceToInstall = errors.New("no go env")
)

View File

@ -28,7 +28,7 @@ func (b *Build) cpGoModulesProject() {
src := v.Module.Dir
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
log.Errorf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
break
} else {

View File

@ -25,36 +25,50 @@ import (
)
// NewInstall creates a Build struct which can install from goc temporary directory
func NewInstall(buildflags string, packages string) *Build {
func NewInstall(buildflags string, packages string) (*Build, error) {
b := &Build{
BuildFlags: buildflags,
Packages: packages,
}
if false == b.validatePackageForInstall() {
log.Fatalln("packages only support . and ./...")
log.Errorln(ErrWrongPackageTypeForInstall)
return nil, ErrWrongPackageTypeForInstall
}
b.MvProjectsToTmp()
return b
return b, nil
}
func (b *Build) Install() {
func (b *Build) Install() error {
log.Println("Go building in temp...")
cmd := exec.Command("/bin/bash", "-c", "go install "+b.BuildFlags+" "+b.Packages)
cmd.Dir = b.TmpWorkingDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
whereToInstall, err := b.findWhereToInstall()
if err != nil {
// ignore the err
log.Errorf("No place to install: %v", err)
}
// Change the temp GOBIN, to force binary install to original place
cmd.Env = append(os.Environ(), fmt.Sprintf("GOBIN=%v", b.findWhereToInstall()))
cmd.Env = append(os.Environ(), fmt.Sprintf("GOBIN=%v", whereToInstall))
if b.NewGOPATH != "" {
// Change to temp GOPATH for go install command
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%v", b.NewGOPATH))
}
log.Printf("go install cmd is: %v", cmd.Args)
out, err := cmd.CombinedOutput()
log.Infof("go install cmd is: %v", cmd.Args)
err = cmd.Start()
if err != nil {
log.Fatalf("Fail to execute: %v. The error is: %v, the stdout/stderr is: %v", cmd.Args, err, string(out))
log.Errorf("Fail to execute: %v. The error is: %v", cmd.Args, err)
return fmt.Errorf("fail to execute: %v: %w", cmd.Args, err)
}
log.Printf("Go install successful. Binary installed in: %v", b.findWhereToInstall())
if err = cmd.Wait(); err != nil {
log.Errorf("go install failed. The error is: %v", err)
return fmt.Errorf("go install failed: %w", err)
}
log.Infof("Go install successful. Binary installed in: %v", whereToInstall)
return nil
}
func (b *Build) validatePackageForInstall() bool {

View File

@ -20,9 +20,10 @@ import (
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
"github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
)
func (b *Build) cpLegacyProject() {
@ -37,7 +38,7 @@ func (b *Build) cpLegacyProject() {
}
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
log.Errorf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
visited[src] = true
@ -73,9 +74,25 @@ func (b *Build) cpDepPackages(pkg *cover.Package, visited map[string]bool) {
dst := filepath.Join(b.TmpDir, "src", dep)
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
log.Errorf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
visited[src] = true
}
}
func (b *Build) cpNonStandardLegacy() {
for _, v := range b.Pkgs {
if v.Name == "main" {
dst := b.TmpDir
src := v.Dir
if err := copy.Copy(src, dst); err != nil {
log.Printf("Failed to Copy the folder from %v to %v, the error is: %v ", src, dst, err)
}
break
} else {
continue
}
}
}

View File

@ -18,6 +18,7 @@ package build
import (
"crypto/sha256"
"errors"
"fmt"
"os"
"path/filepath"
@ -54,10 +55,11 @@ func (b *Build) MvProjectsToTmp() {
return
}
func (b *Build) mvProjectsToTmp() {
func (b *Build) mvProjectsToTmp() error {
path, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directory, the error is: %v", err)
log.Errorf("Cannot get current working directory, the error is: %v", err)
return err
}
b.TmpDir = filepath.Join(os.TempDir(), TmpFolderName(path))
@ -66,20 +68,39 @@ func (b *Build) mvProjectsToTmp() {
// Create a new tmp folder
err = os.MkdirAll(filepath.Join(b.TmpDir, "src"), os.ModePerm)
if err != nil {
log.Fatalf("Fail to create the temporary build directory. The err is: %v", err)
log.Errorf("Fail to create the temporary build directory. The err is: %v", err)
return err
}
log.Printf("Tmp project generated in: %v", b.TmpDir)
// set Build.IsMod flag, so we don't have to call checkIfLegacyProject another time
if b.checkIfLegacyProject() {
b.cpLegacyProject()
} else {
b.IsMod = true
b.cpGoModulesProject()
// traverse pkg list to get project meta info
b.IsMod, b.Root, err = b.traversePkgsList()
if errors.Is(err, ErrShouldNotReached) {
return fmt.Errorf("mvProjectsToTmp with a empty project: %w", err)
}
// we should get corresponding working directory in temporary directory
b.TmpWorkingDir, err = b.getTmpwd()
if err != nil {
log.Errorf("fail to get workding directory in temporary directory: %v", err)
return fmt.Errorf("fail to get workding directory in temporary directory: %w", err)
}
// issue #14
// if b.Root == "", then the project is non-standard project
// known cases:
// 1. a legacy project, but not in any GOPATH, will cause the b.Root == ""
if b.IsMod == false && b.Root != "" {
b.cpLegacyProject()
} else if b.IsMod == true && b.Root != "" {
b.cpGoModulesProject()
} else if b.IsMod == false && b.Root == "" {
b.TmpWorkingDir = b.TmpDir
b.cpNonStandardLegacy()
} else {
return fmt.Errorf("unknown project type: %w", ErrShouldNotReached)
}
b.getTmpwd()
log.Printf("New workingdir in tmp directory in: %v", b.TmpWorkingDir)
log.Infof("New workingdir in tmp directory in: %v", b.TmpWorkingDir)
return nil
}
func TmpFolderName(path string) string {
@ -89,29 +110,35 @@ func TmpFolderName(path string) string {
return "goc-" + h
}
// checkIfLegacyProject Check if it is go module project
// true legacy
// false go mod
func (b *Build) checkIfLegacyProject() bool {
// traversePkgsList travse the Build.Pkgs list
// return Build.IsMod, tell if the project is a mod project
// return Build.Root:
// 1. the project root if it is a mod project,
// 2. current GOPATH if it is a legacy project,
// 3. some non-standard project, which Build.IsMod == false, Build.Root == nil
func (b *Build) traversePkgsList() (isMod bool, root string, err error) {
for _, v := range b.Pkgs {
// get root
b.Root = v.Root
root = v.Root
if v.Module == nil {
return true
return
}
return false
isMod = true
return
}
log.Fatalln("Should never be reached....")
return false
log.Error("should not reach here")
err = ErrShouldNotReached
return
}
// getTmpwd get the corresponding working directory in the temporary working directory
// and store it in the Build.tmpWorkdingDir
func (b *Build) getTmpwd() {
func (b *Build) getTmpwd() (string, error) {
for _, pkg := range b.Pkgs {
path, err := os.Getwd()
if err != nil {
log.Fatalf("Cannot get current working directory, the error is: %v", err)
log.Errorf("cannot get current working directory: %v", err)
return "", fmt.Errorf("cannot get current working directory: %w", err)
}
index := -1
@ -125,33 +152,32 @@ func (b *Build) getTmpwd() {
}
if index == -1 {
log.Fatalf("goc install not executed in project directory.")
return "", ErrGocShouldExecInProject
}
b.TmpWorkingDir = filepath.Join(b.TmpDir, path[len(parentPath):])
// log.Printf("New building directory in: %v", tmpwd)
return
// b.TmpWorkingDir = filepath.Join(b.TmpDir, path[len(parentPath):])
return filepath.Join(b.TmpDir, path[len(parentPath):]), nil
}
log.Fatalln("Should never be reached....")
return
return "", ErrShouldNotReached
}
func (b *Build) findWhereToInstall() string {
func (b *Build) findWhereToInstall() (string, error) {
if GOBIN := os.Getenv("GOBIN"); GOBIN != "" {
return GOBIN
return GOBIN, nil
}
// old GOPATH dir
GOPATH := os.Getenv("GOPATH")
if false == b.IsMod {
for _, v := range b.Pkgs {
return filepath.Join(v.Root, "bin")
if b.Root == "" {
return "", ErrNoplaceToInstall
}
return filepath.Join(b.Root, "bin"), nil
}
if GOPATH != "" {
return filepath.Join(strings.Split(GOPATH, ":")[0], "bin")
return filepath.Join(strings.Split(GOPATH, ":")[0], "bin"), nil
}
return filepath.Join(os.Getenv("HOME"), "go", "bin")
return filepath.Join(os.Getenv("HOME"), "go", "bin"), nil
}
// Clean clears up the temporary workspace

View File

@ -33,7 +33,7 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off")
b := NewInstall("", ".")
b, _ := NewInstall("", ".")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
}
@ -42,7 +42,7 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir)
}
b = NewBuild("", ".", "")
b, _ = NewBuild("", ".", "")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
}
@ -61,7 +61,7 @@ func TestNewDirParseInModProject(t *testing.T) {
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
b := NewInstall("", ".")
b, _ := NewInstall("", ".")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
}
@ -70,7 +70,7 @@ func TestNewDirParseInModProject(t *testing.T) {
t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir)
}
b = NewBuild("", ".", "")
b, _ = NewBuild("", ".", "")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
}
@ -89,7 +89,7 @@ func TestLegacyProjectNotInGoPATH(t *testing.T) {
fmt.Println(gopath)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off")
b := NewBuild("", ".", "")
b, _ := NewBuild("", ".", "")
if b.OriGOPATH != b.NewGOPATH {
t.Fatalf("New GOPATH should be same with old GOPATH, for this kind of project. New: %v, old: %v", b.NewGOPATH, b.OriGOPATH)
}