Merge pull request #39 from lyyyuna/goc-14

fix #14
This commit is contained in:
qiniu-bot 2020-06-17 15:25:27 +08:00 committed by GitHub
commit 4c7721b0e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 446 additions and 100 deletions

View File

@ -17,6 +17,8 @@
package cmd package cmd
import ( import (
"log"
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -27,9 +29,7 @@ var buildCmd = &cobra.Command{
Short: "Do cover for all go files and execute go build command", Short: "Do cover for all go files and execute go build command",
Long: ` 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. 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: ` Example: `
# Build the current binary with cover variables injected. The binary will be generated in the current folder. # Build the current binary with cover variables injected. The binary will be generated in the current folder.
goc build goc build
@ -38,21 +38,13 @@ goc build
goc build --center=http://127.0.0.1:7777 goc build --center=http://127.0.0.1:7777
# Build the current binary with cover variables injected, and redirect output to /to/this/path. # 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". # 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) { Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput) runBuild()
// 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()
return
}, },
} }
@ -63,3 +55,21 @@ func init() {
buildCmd.Flags().StringVar(&buildOutput, "output", "", "it forces build to write the resulting executable or object to the named output file or directory") buildCmd.Flags().StringVar(&buildOutput, "output", "", "it forces build to write the resulting executable or object to the named output file or directory")
rootCmd.AddCommand(buildCmd) rootCmd.AddCommand(buildCmd)
} }
func runBuild() {
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
err = gocBuild.Build()
if err != nil {
log.Fatalf("Fail to build: %v", err)
}
return
}

61
cmd/build_test.go Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright 2020 Qiniu Cloud (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/stretchr/testify/assert"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
var baseDir string
func init() {
baseDir, _ = os.Getwd()
}
func TestGeneratedBinary(t *testing.T) {
startTime := time.Now()
workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
gopath := ""
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
buildFlags, packages, buildOutput = "", ".", ""
runBuild()
obj := filepath.Join(".", "simple-project")
fInfo, err := os.Lstat(obj)
assert.Equal(t, err, nil, "the binary should be generated.")
assert.Equal(t, startTime.Before(fInfo.ModTime()), true, obj+"new binary should be generated, not the old one")
cmd := exec.Command("go", "tool", "objdump", "simple-project")
cmd.Dir = workingDir
out, _ := cmd.CombinedOutput()
cnt := strings.Count(string(out), "main.registerSelf")
assert.Equal(t, cnt > 0, true, "main.registerSelf function should be in the binary")
cnt = strings.Count(string(out), "GoCover")
assert.Equal(t, cnt > 0, true, "GoCover varibale should be in the binary")
}

View File

@ -19,6 +19,7 @@ package cmd
import ( import (
"github.com/qiniu/goc/pkg/build" "github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,8 +28,6 @@ var installCmd = &cobra.Command{
Short: "Do cover for all go files and execute go install command", Short: "Do cover for all go files and execute go install command",
Long: ` 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. 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: ` Example: `
# Install all binaries with cover variables injected. The binary will be installed in $GOPATH/bin or $HOME/go/bin if directory existed. # 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,7 @@ goc install --center=http://127.0.0.1:7777
goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'" goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
`, `,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewInstall(buildFlags, packages) runInstall()
// 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()
}, },
} }
@ -56,3 +48,21 @@ func init() {
addBuildFlags(installCmd.Flags()) addBuildFlags(installCmd.Flags())
rootCmd.AddCommand(installCmd) rootCmd.AddCommand(installCmd)
} }
func runInstall() {
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
err = gocBuild.Install()
if err != nil {
log.Fatalf("Fail to install: %v", err)
}
return
}

83
cmd/install_test.go Normal file
View File

@ -0,0 +1,83 @@
/*
Copyright 2020 Qiniu Cloud (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/stretchr/testify/assert"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestInstalledBinaryForMod(t *testing.T) {
startTime := time.Now()
workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
gopath := filepath.Join(baseDir, "../tests/samples/simple_project", "testhome")
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
buildFlags, packages, buildOutput = "", ".", ""
runInstall()
obj := filepath.Join(gopath, "bin", "simple-project")
fInfo, err := os.Lstat(obj)
assert.Equal(t, err, nil, "the binary should be generated.")
assert.Equal(t, startTime.Before(fInfo.ModTime()), true, obj+"new binary should be generated, not the old one")
cmd := exec.Command("go", "tool", "objdump", "simple-project")
cmd.Dir = workingDir
out, _ := cmd.CombinedOutput()
cnt := strings.Count(string(out), "main.registerSelf")
assert.Equal(t, cnt > 0, true, "main.registerSelf function should be in the binary")
cnt = strings.Count(string(out), "GoCover")
assert.Equal(t, cnt > 0, true, "GoCover varibale should be in the binary")
}
func TestInstalledBinaryForLegacy(t *testing.T) {
startTime := time.Now()
workingDir := filepath.Join(baseDir, "../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
gopath := filepath.Join(baseDir, "../tests/samples/simple_gopath_project")
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off")
buildFlags, packages, buildOutput = "", ".", ""
runInstall()
obj := filepath.Join(gopath, "bin", "simple_gopath_project")
fInfo, err := os.Lstat(obj)
assert.Equal(t, err, nil, "the binary should be generated.")
assert.Equal(t, startTime.Before(fInfo.ModTime()), true, obj+"new binary should be generated, not the old one")
cmd := exec.Command("go", "tool", "objdump", obj)
cmd.Dir = workingDir
out, _ := cmd.CombinedOutput()
cnt := strings.Count(string(out), "main.registerSelf")
assert.Equal(t, cnt > 0, true, "main.registerSelf function should be in the binary")
cnt = strings.Count(string(out), "GoCover")
assert.Equal(t, cnt > 0, true, "GoCover varibale should be in the binary")
}

View File

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

View File

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

53
pkg/build/build_test.go Normal file
View File

@ -0,0 +1,53 @@
/*
Copyright 2020 Qiniu Cloud (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package build
import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
func TestInvalidPackage(t *testing.T) {
workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
gopath := ""
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
_, err := NewBuild("", "example.com/simple-project", "")
assert.Equal(t, err, ErrWrongPackageTypeForBuild, "the package name should be invalid")
}
func TestBasicBuildForModProject(t *testing.T) {
workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
gopath := ""
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
buildFlags, packages, buildOutput := "", ".", ""
gocBuild, err := NewBuild(buildFlags, packages, buildOutput)
assert.Equal(t, err, nil, "should create temporary directory successfully")
err = gocBuild.Build()
assert.Equal(t, err, nil, "temporary directory should build successfully")
}

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 src := v.Module.Dir
if err := copy.Copy(src, dst); err != nil { 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 break
} else { } else {

View File

@ -25,36 +25,50 @@ import (
) )
// NewInstall creates a Build struct which can install from goc temporary directory // 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{ b := &Build{
BuildFlags: buildflags, BuildFlags: buildflags,
Packages: packages, Packages: packages,
} }
if false == b.validatePackageForInstall() { if false == b.validatePackageForInstall() {
log.Fatalln("packages only support . and ./...") log.Errorln(ErrWrongPackageTypeForInstall)
return nil, ErrWrongPackageTypeForInstall
} }
b.MvProjectsToTmp() b.MvProjectsToTmp()
return b return b, nil
} }
func (b *Build) Install() { func (b *Build) Install() error {
log.Println("Go building in temp...") log.Println("Go building in temp...")
cmd := exec.Command("/bin/bash", "-c", "go install "+b.BuildFlags+" "+b.Packages) cmd := exec.Command("/bin/bash", "-c", "go install "+b.BuildFlags+" "+b.Packages)
cmd.Dir = b.TmpWorkingDir 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 // 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 != "" { if b.NewGOPATH != "" {
// Change to temp GOPATH for go install command // Change to temp GOPATH for go install command
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%v", b.NewGOPATH)) cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%v", b.NewGOPATH))
} }
log.Printf("go install cmd is: %v", cmd.Args) log.Infof("go install cmd is: %v", cmd.Args)
out, err := cmd.CombinedOutput() err = cmd.Start()
if err != nil { 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 { func (b *Build) validatePackageForInstall() bool {

24
pkg/build/install_test.go Normal file
View File

@ -0,0 +1,24 @@
package build
import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
func TestBasicInstallForModProject(t *testing.T) {
workingDir := filepath.Join(baseDir, "../tests/samples/simple_project")
gopath := filepath.Join(baseDir, "../tests/samples/simple_project", "testhome")
os.Chdir(workingDir)
os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on")
buildFlags, packages := "", "."
gocBuild, err := NewInstall(buildFlags, packages)
assert.Equal(t, err, nil, "should create temporary directory successfully")
err = gocBuild.Install()
assert.Equal(t, err, nil, "temporary directory should build successfully")
}

View File

@ -20,9 +20,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
log "github.com/sirupsen/logrus"
"github.com/otiai10/copy" "github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover" "github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
) )
func (b *Build) cpLegacyProject() { func (b *Build) cpLegacyProject() {
@ -37,7 +38,7 @@ func (b *Build) cpLegacyProject() {
} }
if err := copy.Copy(src, dst); err != nil { 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 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) dst := filepath.Join(b.TmpDir, "src", dep)
if err := copy.Copy(src, dst); err != nil { 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 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 ( import (
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -47,17 +48,20 @@ func (b *Build) MvProjectsToTmp() {
} }
// fix #14: unable to build project not in GOPATH in legacy mode // fix #14: unable to build project not in GOPATH in legacy mode
// this kind of project does not have a pkg.Root value // this kind of project does not have a pkg.Root value
if b.Root == "" { // go 1.11, 1.12 has no pkg.Root,
// so add b.IsMod == false as secondary judgement
if b.Root == "" && b.IsMod == false {
b.NewGOPATH = b.OriGOPATH b.NewGOPATH = b.OriGOPATH
} }
log.Printf("New GOPATH: %v", b.NewGOPATH) log.Printf("New GOPATH: %v", b.NewGOPATH)
return return
} }
func (b *Build) mvProjectsToTmp() { func (b *Build) mvProjectsToTmp() error {
path, err := os.Getwd() path, err := os.Getwd()
if err != nil { 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)) b.TmpDir = filepath.Join(os.TempDir(), TmpFolderName(path))
@ -66,20 +70,39 @@ func (b *Build) mvProjectsToTmp() {
// Create a new tmp folder // Create a new tmp folder
err = os.MkdirAll(filepath.Join(b.TmpDir, "src"), os.ModePerm) err = os.MkdirAll(filepath.Join(b.TmpDir, "src"), os.ModePerm)
if err != nil { 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) log.Printf("Tmp project generated in: %v", b.TmpDir)
// set Build.IsMod flag, so we don't have to call checkIfLegacyProject another time // traverse pkg list to get project meta info
if b.checkIfLegacyProject() { b.IsMod, b.Root, err = b.traversePkgsList()
b.cpLegacyProject() if errors.Is(err, ErrShouldNotReached) {
} else { return fmt.Errorf("mvProjectsToTmp with a empty project: %w", err)
b.IsMod = true }
b.cpGoModulesProject() // 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 { // go 1.11, 1.12 has no Build.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 { func TmpFolderName(path string) string {
@ -89,29 +112,35 @@ func TmpFolderName(path string) string {
return "goc-" + h return "goc-" + h
} }
// checkIfLegacyProject Check if it is go module project // traversePkgsList travse the Build.Pkgs list
// true legacy // return Build.IsMod, tell if the project is a mod project
// false go mod // return Build.Root:
func (b *Build) checkIfLegacyProject() bool { // 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 { for _, v := range b.Pkgs {
// get root // get root
b.Root = v.Root root = v.Root
if v.Module == nil { if v.Module == nil {
return true return
} }
return false isMod = true
return
} }
log.Fatalln("Should never be reached....") log.Error("should not reach here")
return false err = ErrShouldNotReached
return
} }
// getTmpwd get the corresponding working directory in the temporary working directory // getTmpwd get the corresponding working directory in the temporary working directory
// and store it in the Build.tmpWorkdingDir // and store it in the Build.tmpWorkdingDir
func (b *Build) getTmpwd() { func (b *Build) getTmpwd() (string, error) {
for _, pkg := range b.Pkgs { for _, pkg := range b.Pkgs {
path, err := os.Getwd() path, err := os.Getwd()
if err != nil { 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 index := -1
@ -125,33 +154,32 @@ func (b *Build) getTmpwd() {
} }
if index == -1 { if index == -1 {
log.Fatalf("goc install not executed in project directory.") return "", ErrGocShouldExecInProject
} }
b.TmpWorkingDir = filepath.Join(b.TmpDir, path[len(parentPath):]) // b.TmpWorkingDir = filepath.Join(b.TmpDir, path[len(parentPath):])
// log.Printf("New building directory in: %v", tmpwd) return filepath.Join(b.TmpDir, path[len(parentPath):]), nil
return
} }
log.Fatalln("Should never be reached....") return "", ErrShouldNotReached
return
} }
func (b *Build) findWhereToInstall() string { func (b *Build) findWhereToInstall() (string, error) {
if GOBIN := os.Getenv("GOBIN"); GOBIN != "" { if GOBIN := os.Getenv("GOBIN"); GOBIN != "" {
return GOBIN return GOBIN, nil
} }
// old GOPATH dir // old GOPATH dir
GOPATH := os.Getenv("GOPATH") GOPATH := os.Getenv("GOPATH")
if false == b.IsMod { if false == b.IsMod {
for _, v := range b.Pkgs { if b.Root == "" {
return filepath.Join(v.Root, "bin") return "", ErrNoplaceToInstall
} }
return filepath.Join(b.Root, "bin"), nil
} }
if GOPATH != "" { 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 // Clean clears up the temporary workspace

View File

@ -24,16 +24,22 @@ import (
"testing" "testing"
) )
var baseDir string
func init() {
baseDir, _ = os.Getwd()
}
func TestNewDirParseInLegacyProject(t *testing.T) { func TestNewDirParseInLegacyProject(t *testing.T) {
workingDir := "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project" workingDir := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
gopath, _ := filepath.Abs("../../tests/samples/simple_gopath_project") gopath := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project")
os.Chdir(workingDir) os.Chdir(workingDir)
fmt.Println(gopath)
os.Setenv("GOPATH", gopath) os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off") os.Setenv("GO111MODULE", "off")
b := NewInstall("", ".") b, _ := NewInstall("", ".")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) { if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir) t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
} }
@ -42,7 +48,7 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir) 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) { if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir) t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
} }
@ -53,7 +59,7 @@ func TestNewDirParseInLegacyProject(t *testing.T) {
} }
func TestNewDirParseInModProject(t *testing.T) { func TestNewDirParseInModProject(t *testing.T) {
workingDir := "../../tests/samples/simple_project" workingDir := filepath.Join(baseDir, "../../tests/samples/simple_project")
gopath := "" gopath := ""
os.Chdir(workingDir) os.Chdir(workingDir)
@ -61,7 +67,7 @@ func TestNewDirParseInModProject(t *testing.T) {
os.Setenv("GOPATH", gopath) os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "on") os.Setenv("GO111MODULE", "on")
b := NewInstall("", ".") b, _ := NewInstall("", ".")
if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) { if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir) t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
} }
@ -70,7 +76,7 @@ func TestNewDirParseInModProject(t *testing.T) {
t.Fatalf("The New GOPATH is wrong. newgopath: %v, tmpdir: %v", b.NewGOPATH, b.TmpDir) 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) { if -1 == strings.Index(b.TmpWorkingDir, b.TmpDir) {
t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir) t.Fatalf("Directory parse error. newwd: %v, tmpdir: %v", b.TmpWorkingDir, b.TmpDir)
} }
@ -82,15 +88,21 @@ func TestNewDirParseInModProject(t *testing.T) {
// Test #14 // Test #14
func TestLegacyProjectNotInGoPATH(t *testing.T) { func TestLegacyProjectNotInGoPATH(t *testing.T) {
workingDir := "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project" workingDir := filepath.Join(baseDir, "../../tests/samples/simple_gopath_project/src/qiniu.com/simple_gopath_project")
gopath := "" gopath := ""
os.Chdir(workingDir) os.Chdir(workingDir)
fmt.Println(gopath) fmt.Println(gopath)
os.Setenv("GOPATH", gopath) os.Setenv("GOPATH", gopath)
os.Setenv("GO111MODULE", "off") os.Setenv("GO111MODULE", "off")
b := NewBuild("", ".", "")
b, _ := NewBuild("", ".", "")
if b.OriGOPATH != b.NewGOPATH { 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) t.Fatalf("New GOPATH should be same with old GOPATH, for this kind of project. New: %v, old: %v", b.NewGOPATH, b.OriGOPATH)
} }
_, err := os.Stat(filepath.Join(b.TmpDir, "main.go"))
if err != nil {
t.Fatalf("There should be a main.go in temporary directory directly, the error: %v", err)
}
} }

View File

@ -56,6 +56,7 @@ var _ = Describe("E2E", func() {
testProjDir := filepath.Join(TESTS_ROOT, "samples/simple_project") testProjDir := filepath.Join(TESTS_ROOT, "samples/simple_project")
cmd := exec.Command("goc", "build", "--debug") cmd := exec.Command("goc", "build", "--debug")
cmd.Dir = testProjDir cmd.Dir = testProjDir
cmd.Env = append(os.Environ(), "GO111MODULE=on")
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
Expect(err).To(BeNil(), "goc build on this project should be successful", string(out)) Expect(err).To(BeNil(), "goc build on this project should be successful", string(out))

View File

@ -22,4 +22,4 @@ chmod +x /home/runner/tools/e2e.test/e2e.test
export PATH=/home/runner/tools/e2e.test:$PATH export PATH=/home/runner/tools/e2e.test:$PATH
cd e2e cd e2e
e2e.test ./... e2e.test -test.v ./...