mirror of
https://github.com/beego/bee.git
synced 2024-11-16 15:20:55 +00:00
338dfc65ed
Added enhanced logging while using bee to scaffold code Fixed some typos and errors Added more error handling Added more docs
632 lines
13 KiB
Go
632 lines
13 KiB
Go
// Copyright 2013 bee authors
|
|
//
|
|
// 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 main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"compress/gzip"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
path "path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
var cmdPack = &Command{
|
|
CustomFlags: true,
|
|
UsageLine: "pack",
|
|
Short: "Compress a beego project into a single file",
|
|
Long: `
|
|
Pack is used to compress a beego project into a single file.
|
|
This eases the deployment by extracting the zip file to a server.
|
|
|
|
-p app path (default is the current path).
|
|
-b build specify platform app (default: true).
|
|
-ba additional args of go build
|
|
-be=[] additional ENV Variables of go build. eg: GOARCH=arm
|
|
-o compressed file output dir. default use current path
|
|
-f="" format: tar.gz, zip (default: tar.gz)
|
|
-exp="" relpath exclude prefix (default: .). use : as separator
|
|
-exs="" relpath exclude suffix (default: .go:.DS_Store:.tmp). use : as separator
|
|
all path use : as separator
|
|
-exr=[] file/directory name exclude by Regexp (default: ^).
|
|
-fs=false follow symlink (default: false).
|
|
-ss=false skip symlink (default: false)
|
|
default embed symlink into compressed file
|
|
-v=false verbose
|
|
`,
|
|
}
|
|
|
|
var (
|
|
appPath string
|
|
excludeP string
|
|
excludeS string
|
|
outputP string
|
|
excludeR ListOpts
|
|
fsym bool
|
|
ssym bool
|
|
build bool
|
|
buildArgs string
|
|
buildEnvs ListOpts
|
|
verbose bool
|
|
format string
|
|
w io.Writer
|
|
)
|
|
|
|
type ListOpts []string
|
|
|
|
func (opts *ListOpts) String() string {
|
|
return fmt.Sprint(*opts)
|
|
}
|
|
|
|
func (opts *ListOpts) Set(value string) error {
|
|
*opts = append(*opts, value)
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
fs := flag.NewFlagSet("pack", flag.ContinueOnError)
|
|
fs.StringVar(&appPath, "p", "", "app path. default is current path")
|
|
fs.BoolVar(&build, "b", true, "build specify platform app")
|
|
fs.StringVar(&buildArgs, "ba", "", "additional args of go build")
|
|
fs.Var(&buildEnvs, "be", "additional ENV Variables of go build. eg: GOARCH=arm")
|
|
fs.StringVar(&outputP, "o", "", "compressed file output dir. default use current path")
|
|
fs.StringVar(&format, "f", "tar.gz", "format. [ tar.gz / zip ]")
|
|
fs.StringVar(&excludeP, "exp", ".", "path exclude prefix. use : as separator")
|
|
fs.StringVar(&excludeS, "exs", ".go:.DS_Store:.tmp", "path exclude suffix. use : as separator")
|
|
fs.Var(&excludeR, "exr", "filename exclude by Regexp")
|
|
fs.BoolVar(&fsym, "fs", false, "follow symlink")
|
|
fs.BoolVar(&ssym, "ss", false, "skip symlink")
|
|
fs.BoolVar(&verbose, "v", false, "verbose")
|
|
cmdPack.Flag = *fs
|
|
cmdPack.Run = packApp
|
|
w = NewColorWriter(os.Stdout)
|
|
}
|
|
|
|
func exitPrint(con string) {
|
|
fmt.Fprintln(os.Stderr, con)
|
|
os.Exit(2)
|
|
}
|
|
|
|
type walker interface {
|
|
isExclude(string) bool
|
|
isEmpty(string) bool
|
|
relName(string) string
|
|
virPath(string) string
|
|
compress(string, string, os.FileInfo) (bool, error)
|
|
walkRoot(string) error
|
|
}
|
|
|
|
type byName []os.FileInfo
|
|
|
|
func (f byName) Len() int { return len(f) }
|
|
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
|
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
|
|
type walkFileTree struct {
|
|
wak walker
|
|
prefix string
|
|
excludePrefix []string
|
|
excludeRegexp []*regexp.Regexp
|
|
excludeSuffix []string
|
|
allfiles map[string]bool
|
|
}
|
|
|
|
func (wft *walkFileTree) setPrefix(prefix string) {
|
|
wft.prefix = prefix
|
|
}
|
|
|
|
func (wft *walkFileTree) isExclude(fPath string) bool {
|
|
if fPath == "" {
|
|
return true
|
|
}
|
|
|
|
for _, prefix := range wft.excludePrefix {
|
|
if strings.HasPrefix(fPath, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
for _, suffix := range wft.excludeSuffix {
|
|
if strings.HasSuffix(fPath, suffix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (wft *walkFileTree) isExcludeName(name string) bool {
|
|
for _, r := range wft.excludeRegexp {
|
|
if r.MatchString(name) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (wft *walkFileTree) isEmpty(fpath string) bool {
|
|
fh, _ := os.Open(fpath)
|
|
defer fh.Close()
|
|
infos, _ := fh.Readdir(-1)
|
|
for _, fi := range infos {
|
|
fn := fi.Name()
|
|
fp := path.Join(fpath, fn)
|
|
if wft.isExclude(wft.virPath(fp)) {
|
|
continue
|
|
}
|
|
if wft.isExcludeName(fn) {
|
|
continue
|
|
}
|
|
if fi.Mode()&os.ModeSymlink > 0 {
|
|
continue
|
|
}
|
|
if fi.IsDir() && wft.isEmpty(fp) {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (wft *walkFileTree) relName(fpath string) string {
|
|
name, _ := path.Rel(wft.prefix, fpath)
|
|
return name
|
|
}
|
|
|
|
func (wft *walkFileTree) virPath(fpath string) string {
|
|
name := fpath[len(wft.prefix):]
|
|
if name == "" {
|
|
return ""
|
|
}
|
|
name = name[1:]
|
|
name = path.ToSlash(name)
|
|
return name
|
|
}
|
|
|
|
func (wft *walkFileTree) readDir(dirname string) ([]os.FileInfo, error) {
|
|
f, err := os.Open(dirname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list, err := f.Readdir(-1)
|
|
f.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Sort(byName(list))
|
|
return list, nil
|
|
}
|
|
|
|
func (wft *walkFileTree) walkLeaf(fpath string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fpath == outputP {
|
|
return nil
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
if ssym && fi.Mode()&os.ModeSymlink > 0 {
|
|
return nil
|
|
}
|
|
|
|
name := wft.virPath(fpath)
|
|
|
|
if wft.allfiles[name] {
|
|
return nil
|
|
}
|
|
|
|
if added, err := wft.wak.compress(name, fpath, fi); added {
|
|
if verbose {
|
|
fmt.Fprintf(w, "\t%s%scompressed%s\t %s%s\n", "\x1b[32m", "\x1b[1m", "\x1b[21m", name, "\x1b[0m")
|
|
}
|
|
wft.allfiles[name] = true
|
|
return err
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (wft *walkFileTree) iterDirectory(fpath string, fi os.FileInfo) error {
|
|
doFSym := fsym && fi.Mode()&os.ModeSymlink > 0
|
|
if doFSym {
|
|
nfi, err := os.Stat(fpath)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
fi = nfi
|
|
}
|
|
|
|
relPath := wft.virPath(fpath)
|
|
|
|
if len(relPath) > 0 {
|
|
if wft.isExcludeName(fi.Name()) {
|
|
return nil
|
|
}
|
|
|
|
if wft.isExclude(relPath) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
err := wft.walkLeaf(fpath, fi, nil)
|
|
if err != nil {
|
|
if fi.IsDir() && err == path.SkipDir {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
list, err := wft.readDir(fpath)
|
|
if err != nil {
|
|
return wft.walkLeaf(fpath, fi, err)
|
|
}
|
|
|
|
for _, fileInfo := range list {
|
|
err = wft.iterDirectory(path.Join(fpath, fileInfo.Name()), fileInfo)
|
|
if err != nil {
|
|
if !fileInfo.IsDir() || err != path.SkipDir {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (wft *walkFileTree) walkRoot(root string) error {
|
|
wft.prefix = root
|
|
fi, err := os.Stat(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return wft.iterDirectory(root, fi)
|
|
}
|
|
|
|
type tarWalk struct {
|
|
walkFileTree
|
|
tw *tar.Writer
|
|
}
|
|
|
|
func (wft *tarWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
|
|
isSym := fi.Mode()&os.ModeSymlink > 0
|
|
link := ""
|
|
if isSym {
|
|
link, _ = os.Readlink(fpath)
|
|
}
|
|
|
|
hdr, err := tar.FileInfoHeader(fi, link)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
hdr.Name = name
|
|
|
|
tw := wft.tw
|
|
err = tw.WriteHeader(hdr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if isSym == false {
|
|
fr, err := os.Open(fpath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer CloseFile(fr)
|
|
_, err = io.Copy(tw, fr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
tw.Flush()
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
type zipWalk struct {
|
|
walkFileTree
|
|
zw *zip.Writer
|
|
}
|
|
|
|
func (wft *zipWalk) compress(name, fpath string, fi os.FileInfo) (bool, error) {
|
|
isSym := fi.Mode()&os.ModeSymlink > 0
|
|
|
|
hdr, err := zip.FileInfoHeader(fi)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
hdr.Name = name
|
|
|
|
zw := wft.zw
|
|
w, err := zw.CreateHeader(hdr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if isSym == false {
|
|
fr, err := os.Open(fpath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer CloseFile(fr)
|
|
_, err = io.Copy(w, fr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
} else {
|
|
var link string
|
|
if link, err = os.Readlink(fpath); err != nil {
|
|
return false, err
|
|
}
|
|
_, err = w.Write([]byte(link))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func packDirectory(excludePrefix []string, excludeSuffix []string,
|
|
excludeRegexp []*regexp.Regexp, includePath ...string) (err error) {
|
|
|
|
ColorLog("Excluding relpath prefix: %s\n", strings.Join(excludePrefix, ":"))
|
|
ColorLog("Excluding relpath suffix: %s\n", strings.Join(excludeSuffix, ":"))
|
|
if len(excludeRegexp) > 0 {
|
|
ColorLog("Excluding filename regex: `%s`\n", strings.Join(excludeR, "`, `"))
|
|
}
|
|
|
|
w, err := os.OpenFile(outputP, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var wft walker
|
|
|
|
if format == "zip" {
|
|
walk := new(zipWalk)
|
|
zw := zip.NewWriter(w)
|
|
defer func() {
|
|
zw.Close()
|
|
}()
|
|
walk.allfiles = make(map[string]bool)
|
|
walk.zw = zw
|
|
walk.wak = walk
|
|
walk.excludePrefix = excludePrefix
|
|
walk.excludeSuffix = excludeSuffix
|
|
walk.excludeRegexp = excludeRegexp
|
|
wft = walk
|
|
} else {
|
|
walk := new(tarWalk)
|
|
cw := gzip.NewWriter(w)
|
|
tw := tar.NewWriter(cw)
|
|
|
|
defer func() {
|
|
tw.Flush()
|
|
cw.Flush()
|
|
tw.Close()
|
|
cw.Close()
|
|
}()
|
|
walk.allfiles = make(map[string]bool)
|
|
walk.tw = tw
|
|
walk.wak = walk
|
|
walk.excludePrefix = excludePrefix
|
|
walk.excludeSuffix = excludeSuffix
|
|
walk.excludeRegexp = excludeRegexp
|
|
wft = walk
|
|
}
|
|
|
|
for _, p := range includePath {
|
|
err = wft.walkRoot(p)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func isBeegoProject(thePath string) bool {
|
|
fh, _ := os.Open(thePath)
|
|
fis, _ := fh.Readdir(-1)
|
|
regex := regexp.MustCompile(`(?s)package main.*?import.*?\(.*?github.com/astaxie/beego".*?\).*func main()`)
|
|
for _, fi := range fis {
|
|
if fi.IsDir() == false && strings.HasSuffix(fi.Name(), ".go") {
|
|
data, err := ioutil.ReadFile(path.Join(thePath, fi.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(regex.Find(data)) > 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func packApp(cmd *Command, args []string) int {
|
|
ShowShortVersionBanner()
|
|
|
|
curPath, _ := os.Getwd()
|
|
thePath := ""
|
|
|
|
nArgs := []string{}
|
|
has := false
|
|
for _, a := range args {
|
|
if a != "" && a[0] == '-' {
|
|
has = true
|
|
}
|
|
if has {
|
|
nArgs = append(nArgs, a)
|
|
}
|
|
}
|
|
cmdPack.Flag.Parse(nArgs)
|
|
|
|
if path.IsAbs(appPath) == false {
|
|
appPath = path.Join(curPath, appPath)
|
|
}
|
|
|
|
thePath, err := path.Abs(appPath)
|
|
if err != nil {
|
|
exitPrint(fmt.Sprintf("Wrong app path: %s", thePath))
|
|
}
|
|
if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false {
|
|
exitPrint(fmt.Sprintf("App path does not exist: %s", thePath))
|
|
}
|
|
|
|
if isBeegoProject(thePath) == false {
|
|
exitPrint(fmt.Sprintf("Bee does not support non Beego project"))
|
|
}
|
|
|
|
ColorLog("Packaging application: %s\n", thePath)
|
|
|
|
appName := path.Base(thePath)
|
|
|
|
goos := runtime.GOOS
|
|
if v, found := syscall.Getenv("GOOS"); found {
|
|
goos = v
|
|
}
|
|
goarch := runtime.GOARCH
|
|
if v, found := syscall.Getenv("GOARCH"); found {
|
|
goarch = v
|
|
}
|
|
|
|
str := strconv.FormatInt(time.Now().UnixNano(), 10)[9:]
|
|
|
|
tmpdir := path.Join(os.TempDir(), "beePack-"+str)
|
|
|
|
os.Mkdir(tmpdir, 0700)
|
|
|
|
if build {
|
|
ColorLog("Building application...\n")
|
|
var envs []string
|
|
for _, env := range buildEnvs {
|
|
parts := strings.SplitN(env, "=", 2)
|
|
if len(parts) == 2 {
|
|
k, v := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
|
if len(k) > 0 && len(v) > 0 {
|
|
switch k {
|
|
case "GOOS":
|
|
goos = v
|
|
case "GOARCH":
|
|
goarch = v
|
|
default:
|
|
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
os.Setenv("GOOS", goos)
|
|
os.Setenv("GOARCH", goarch)
|
|
|
|
ColorLog("Env: GOOS=%s GOARCH=%s\n", goos, goarch)
|
|
|
|
binPath := path.Join(tmpdir, appName)
|
|
if goos == "windows" {
|
|
binPath += ".exe"
|
|
}
|
|
|
|
args := []string{"build", "-o", binPath}
|
|
if len(buildArgs) > 0 {
|
|
args = append(args, strings.Fields(buildArgs)...)
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Fprintf(w, "\t%s%s+ go %s%s%s\n", "\x1b[32m", "\x1b[1m", strings.Join(args, " "), "\x1b[21m", "\x1b[0m")
|
|
}
|
|
|
|
execmd := exec.Command("go", args...)
|
|
execmd.Env = append(os.Environ(), envs...)
|
|
execmd.Stdout = os.Stdout
|
|
execmd.Stderr = os.Stderr
|
|
execmd.Dir = thePath
|
|
err = execmd.Run()
|
|
if err != nil {
|
|
exitPrint(err.Error())
|
|
}
|
|
|
|
ColorLog("Build successful\n")
|
|
}
|
|
|
|
switch format {
|
|
case "zip":
|
|
default:
|
|
format = "tar.gz"
|
|
}
|
|
|
|
outputN := appName + "." + format
|
|
|
|
if outputP == "" || path.IsAbs(outputP) == false {
|
|
outputP = path.Join(curPath, outputP)
|
|
}
|
|
|
|
if _, err := os.Stat(outputP); err != nil {
|
|
err = os.MkdirAll(outputP, 0755)
|
|
if err != nil {
|
|
exitPrint(err.Error())
|
|
}
|
|
}
|
|
|
|
outputP = path.Join(outputP, outputN)
|
|
|
|
var exp, exs []string
|
|
for _, p := range strings.Split(excludeP, ":") {
|
|
if len(p) > 0 {
|
|
exp = append(exp, p)
|
|
}
|
|
}
|
|
for _, p := range strings.Split(excludeS, ":") {
|
|
if len(p) > 0 {
|
|
exs = append(exs, p)
|
|
}
|
|
}
|
|
|
|
var exr []*regexp.Regexp
|
|
for _, r := range excludeR {
|
|
if len(r) > 0 {
|
|
if re, err := regexp.Compile(r); err != nil {
|
|
exitPrint(err.Error())
|
|
} else {
|
|
exr = append(exr, re)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = packDirectory(exp, exs, exr, tmpdir, thePath)
|
|
if err != nil {
|
|
exitPrint(err.Error())
|
|
}
|
|
|
|
ColorLog("Writing to output: `%s`\n", outputP)
|
|
return 0
|
|
}
|