From bbf565e9d8aa7d477ae10c47cacee99d3687f8f9 Mon Sep 17 00:00:00 2001 From: slene Date: Thu, 27 Jun 2013 10:15:54 +0800 Subject: [PATCH] add pack feature, not can pack project into compressed file --- main.go | 12 +- pack.go | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 465 insertions(+), 3 deletions(-) create mode 100644 pack.go diff --git a/main.go b/main.go index bd309e9..8964873 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,7 @@ func (c *Command) Runnable() bool { var commands = []*Command{ cmdCreate, cmdStart, + cmdPack, //cmdReStart, } @@ -82,8 +83,13 @@ func main() { if cmd.CustomFlags { args = args[1:] } else { - cmd.Flag.Parse(args[1:]) - args = cmd.Flag.Args() + if len(args) > 2 { + cmd.Flag.Parse(args[2:]) + args = append([]string{args[1]}, cmd.Flag.Args()...) + } else { + cmd.Flag.Parse(args[1:]) + args = cmd.Flag.Args() + } } cmd.Run(cmd, args) os.Exit(2) @@ -91,7 +97,7 @@ func main() { } } - fmt.Fprintf(os.Stderr, "go: unknown subcommand %q\nRun 'go help' for usage.\n", args[0]) + fmt.Fprintf(os.Stderr, "bee: unknown subcommand %q\nRun 'bee help' for usage.\n", args[0]) os.Exit(2) } diff --git a/pack.go b/pack.go new file mode 100644 index 0000000..ec1de60 --- /dev/null +++ b/pack.go @@ -0,0 +1,456 @@ +package main + +import ( + "archive/tar" + "archive/zip" + "compress/gzip" + "flag" + "fmt" + "io" + "os" + "os/exec" + path "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "syscall" + "time" +) + +var cmdPack = &Command{ + UsageLine: "pack [appPath]", + Short: "pack an beego project into one execute file", + Long: ` +compress an project + +-b build specify platform app. default true +-o compressed file output path. default use appname +-f format. [ tar.gz / zip ]. default tar.gz. note: zip doesn't support embed symlink, skip it +-exp path exclude prefix +-exs path exclude suffix. default: .go:.DS_Store:.tmp + all path use : as separator +-fs follow symlink. default false +-ss skip symlink. default false + default embed symlink into compressed file +-v verbose +`, +} + +var ( + excludeP string + excludeS string + outputP string + fsym bool + ssym bool + build bool + verbose bool + format string +) + +func init() { + fs := flag.NewFlagSet("pack", flag.ExitOnError) + fs.StringVar(&excludeP, "exp", "", "") + fs.StringVar(&excludeS, "exs", ".go:.DS_Store", "") + fs.StringVar(&outputP, "o", "", "") + fs.BoolVar(&build, "b", true, "") + fs.BoolVar(&fsym, "fs", false, "") + fs.BoolVar(&ssym, "ss", false, "") + fs.BoolVar(&verbose, "v", false, "") + fs.StringVar(&format, "f", "tar.gz", "") + cmdPack.Flag = *fs + cmdPack.Run = packApp +} + +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 + excludeSuffix []string +} + +func (wft *walkFileTree) setPrefix(prefix string) { + wft.prefix = prefix +} + +func (wft *walkFileTree) isExclude(name string) bool { + if name == "" { + return true + } + for _, prefix := range wft.excludePrefix { + if strings.HasPrefix(name, prefix) { + return true + } + } + for _, suffix := range wft.excludeSuffix { + if strings.HasSuffix(name, suffix) { + 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 { + fp := path.Join(fpath, fi.Name()) + if wft.isExclude(wft.virPath(fp)) { + 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:] + 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 fi.IsDir() { + return nil + } + + name := wft.virPath(fpath) + if wft.isExclude(name) { + return nil + } + + if ssym && fi.Mode()&os.ModeSymlink > 0 { + return nil + } + + if added, err := wft.wak.compress(name, fpath, fi); added { + if verbose { + fmt.Printf("Compressed: %s\n", name) + } + return err + } else { + 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 + } + + 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 fr.Close() + _, 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 + if isSym { + // golang1.1 doesn't support embed symlink + // what i miss something? + return false, nil + } + + 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 fr.Close() + _, err = io.Copy(w, fr) + if err != nil { + return false, err + } + } + + return true, nil +} + +func packDirectory(excludePrefix []string, excludeSuffix []string, includePath ...string) (err error) { + fmt.Printf("exclude prefix: %s\n", strings.Join(excludePrefix, ":")) + fmt.Printf("exclude suffix: %s\n", strings.Join(excludeSuffix, ":")) + + 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.zw = zw + walk.wak = walk + walk.excludePrefix = excludePrefix + walk.excludeSuffix = excludeSuffix + 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.tw = tw + walk.wak = walk + walk.excludePrefix = excludePrefix + walk.excludeSuffix = excludeSuffix + wft = walk + } + + for _, p := range includePath { + err = wft.walkRoot(p) + if err != nil { + return + } + } + + return +} + +func packApp(cmd *Command, args []string) { + if len(args) == 0 { + fmt.Fprintln(os.Stderr, "need appPath") + cmdPack.Usage() + } + + curPath, _ := os.Getwd() + thePath := "" + appPath := args[0] + + if path.IsAbs(appPath) == false { + thePath = path.Join(curPath, appPath) + } + thePath, err := path.Abs(thePath) + 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("not exist app path: %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:] + + gobin := path.Join(runtime.GOROOT(), "bin", "go") + tmpdir := path.Join(os.TempDir(), "beePack-"+str) + + os.Mkdir(tmpdir, 0700) + + if build { + fmt.Println("GOOS", goos, "GOARCH", goarch) + fmt.Println("build", appName) + + os.Setenv("GOOS", goos) + os.Setenv("GOARCH", goarch) + + binPath := path.Join(tmpdir, appName) + execmd := exec.Command(gobin, "build", "-o", binPath) + execmd.Stdout = os.Stdout + execmd.Stderr = os.Stderr + err = execmd.Run() + if err != nil { + exitPrint(err.Error()) + } + + fmt.Println("build success") + } + + switch format { + case "zip": + default: + format = "tar.gz" + } + + if outputP == "" { + outputP = path.Join(curPath, appName+"."+format) + } + + if stat, err := os.Stat(outputP); err == nil && stat.IsDir() { + outputP = path.Join(outputP, appName+"."+format) + } + + 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) + } + } + + err = packDirectory(exp, exs, thePath, tmpdir) + if err != nil { + exitPrint(err.Error()) + } + + fmt.Printf("file write to `%s`\n", outputP) +}