1
0
mirror of https://github.com/beego/bee.git synced 2024-11-10 15:00:54 +00:00
bee/pack.go
Faissal Elamraoui cf7aef47f0 Implementing the new logging infrastructure
Moved logging to the new logging infrastructure by removing the use of
ColorLog() function. Added more documentation. Also fixed some typos in
comments and function names.
2016-11-13 15:14:48 +01:00

608 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"
"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)
}
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) {
logger.Infof("Excluding relpath prefix: %s", strings.Join(excludePrefix, ":"))
logger.Infof("Excluding relpath suffix: %s", strings.Join(excludeSuffix, ":"))
if len(excludeRegexp) > 0 {
logger.Infof("Excluding filename regex: `%s`", 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 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 {
logger.Fatalf("Wrong application path: %s", thePath)
}
if stat, err := os.Stat(thePath); os.IsNotExist(err) || stat.IsDir() == false {
logger.Fatalf("Application path does not exist: %s", thePath)
}
if isBeegoProject(thePath) == false {
logger.Fatal("Bee does not support non Beego project")
}
logger.Infof("Packaging application on '%s'...", 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 {
logger.Info("Building application...")
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)
logger.Infof("Using: GOOS=%s GOARCH=%s", 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 {
logger.Fatal(err.Error())
}
logger.Success("Build successful!")
}
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 {
logger.Fatal(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 {
logger.Fatal(err.Error())
} else {
exr = append(exr, re)
}
}
}
err = packDirectory(exp, exs, exr, tmpdir, thePath)
if err != nil {
logger.Fatal(err.Error())
}
logger.Infof("Writing to output: %s", outputP)
return 0
}