github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/cmd/aebundler/aebundler.go (about) 1 // Copyright 2015 Google Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // Program aebundler turns a Go app into a fully self-contained tar file. 6 // The app and its subdirectories (if any) are placed under "." 7 // and the dependencies from $GOPATH are placed under ./_gopath/src. 8 // A main func is synthesized if one does not exist. 9 // 10 // A sample Dockerfile to be used with this bundler could look like this: 11 // FROM gcr.io/google_appengine/go-compat 12 // ADD . /app 13 // RUN GOPATH=/app/_gopath go build -tags appenginevm -o /app/_ah/exe 14 package main 15 16 import ( 17 "archive/tar" 18 "flag" 19 "fmt" 20 "go/ast" 21 "go/build" 22 "go/parser" 23 "go/token" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 ) 30 31 var ( 32 output = flag.String("o", "", "name of output tar file or '-' for stdout") 33 rootDir = flag.String("root", ".", "directory name of application root") 34 vm = flag.Bool("vm", true, "bundle a Managed VM app") 35 36 skipFiles = map[string]bool{ 37 ".git": true, 38 ".gitconfig": true, 39 ".hg": true, 40 ".travis.yml": true, 41 } 42 ) 43 44 const ( 45 newMain = `package main 46 import "google.golang.org/appengine" 47 func main() { 48 appengine.Main() 49 } 50 ` 51 ) 52 53 func usage() { 54 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 55 fmt.Fprintf(os.Stderr, "\t%s -o <file.tar|->\tBundle app to named tar file or stdout\n", os.Args[0]) 56 fmt.Fprintf(os.Stderr, "\noptional arguments:\n") 57 flag.PrintDefaults() 58 } 59 60 func main() { 61 flag.Usage = usage 62 flag.Parse() 63 64 var tags []string 65 if *vm { 66 tags = append(tags, "appenginevm") 67 } else { 68 tags = append(tags, "appengine") 69 } 70 71 tarFile := *output 72 if tarFile == "" { 73 usage() 74 errorf("Required -o flag not specified.") 75 } 76 77 app, err := analyze(tags) 78 if err != nil { 79 errorf("Error analyzing app: %v", err) 80 } 81 if err := app.bundle(tarFile); err != nil { 82 errorf("Unable to bundle app: %v", err) 83 } 84 } 85 86 // errorf prints the error message and exits. 87 func errorf(format string, a ...interface{}) { 88 fmt.Fprintf(os.Stderr, "aebundler: "+format+"\n", a...) 89 os.Exit(1) 90 } 91 92 type app struct { 93 hasMain bool 94 appFiles []string 95 imports map[string]string 96 } 97 98 // analyze checks the app for building with the given build tags and returns hasMain, 99 // app files, and a map of full directory import names to original import names. 100 func analyze(tags []string) (*app, error) { 101 ctxt := buildContext(tags) 102 hasMain, appFiles, err := checkMain(ctxt) 103 if err != nil { 104 return nil, err 105 } 106 gopath := filepath.SplitList(ctxt.GOPATH) 107 im, err := imports(ctxt, *rootDir, gopath) 108 return &app{ 109 hasMain: hasMain, 110 appFiles: appFiles, 111 imports: im, 112 }, err 113 } 114 115 // buildContext returns the context for building the source. 116 func buildContext(tags []string) *build.Context { 117 return &build.Context{ 118 GOARCH: build.Default.GOARCH, 119 GOOS: build.Default.GOOS, 120 GOROOT: build.Default.GOROOT, 121 GOPATH: build.Default.GOPATH, 122 Compiler: build.Default.Compiler, 123 BuildTags: append(build.Default.BuildTags, tags...), 124 } 125 } 126 127 // bundle bundles the app into the named tarFile ("-"==stdout). 128 func (s *app) bundle(tarFile string) (err error) { 129 var out io.Writer 130 if tarFile == "-" { 131 out = os.Stdout 132 } else { 133 f, err := os.Create(tarFile) 134 if err != nil { 135 return err 136 } 137 defer func() { 138 if cerr := f.Close(); err == nil { 139 err = cerr 140 } 141 }() 142 out = f 143 } 144 tw := tar.NewWriter(out) 145 146 for srcDir, importName := range s.imports { 147 dstDir := "_gopath/src/" + importName 148 if err = copyTree(tw, dstDir, srcDir); err != nil { 149 return fmt.Errorf("unable to copy directory %v to %v: %v", srcDir, dstDir, err) 150 } 151 } 152 if err := copyTree(tw, ".", *rootDir); err != nil { 153 return fmt.Errorf("unable to copy root directory to /app: %v", err) 154 } 155 if !s.hasMain { 156 if err := synthesizeMain(tw, s.appFiles); err != nil { 157 return fmt.Errorf("unable to synthesize new main func: %v", err) 158 } 159 } 160 161 if err := tw.Close(); err != nil { 162 return fmt.Errorf("unable to close tar file %v: %v", tarFile, err) 163 } 164 return nil 165 } 166 167 // synthesizeMain generates a new main func and writes it to the tarball. 168 func synthesizeMain(tw *tar.Writer, appFiles []string) error { 169 appMap := make(map[string]bool) 170 for _, f := range appFiles { 171 appMap[f] = true 172 } 173 var f string 174 for i := 0; i < 100; i++ { 175 f = fmt.Sprintf("app_main%d.go", i) 176 if !appMap[filepath.Join(*rootDir, f)] { 177 break 178 } 179 } 180 if appMap[filepath.Join(*rootDir, f)] { 181 return fmt.Errorf("unable to find unique name for %v", f) 182 } 183 hdr := &tar.Header{ 184 Name: f, 185 Mode: 0644, 186 Size: int64(len(newMain)), 187 } 188 if err := tw.WriteHeader(hdr); err != nil { 189 return fmt.Errorf("unable to write header for %v: %v", f, err) 190 } 191 if _, err := tw.Write([]byte(newMain)); err != nil { 192 return fmt.Errorf("unable to write %v to tar file: %v", f, err) 193 } 194 return nil 195 } 196 197 // imports returns a map of all import directories (recursively) used by the app. 198 // The return value maps full directory names to original import names. 199 func imports(ctxt *build.Context, srcDir string, gopath []string) (map[string]string, error) { 200 pkg, err := ctxt.ImportDir(srcDir, 0) 201 if err != nil { 202 return nil, fmt.Errorf("unable to analyze source: %v", err) 203 } 204 205 // Resolve all non-standard-library imports 206 result := make(map[string]string) 207 for _, v := range pkg.Imports { 208 if !strings.Contains(v, ".") { 209 continue 210 } 211 src, err := findInGopath(v, gopath) 212 if err != nil { 213 return nil, fmt.Errorf("unable to find import %v in gopath %v: %v", v, gopath, err) 214 } 215 result[src] = v 216 im, err := imports(ctxt, src, gopath) 217 if err != nil { 218 return nil, fmt.Errorf("unable to parse package %v: %v", src, err) 219 } 220 for k, v := range im { 221 result[k] = v 222 } 223 } 224 return result, nil 225 } 226 227 // findInGopath searches the gopath for the named import directory. 228 func findInGopath(dir string, gopath []string) (string, error) { 229 for _, v := range gopath { 230 dst := filepath.Join(v, "src", dir) 231 if _, err := os.Stat(dst); err == nil { 232 return dst, nil 233 } 234 } 235 return "", fmt.Errorf("unable to find package %v in gopath %v", dir, gopath) 236 } 237 238 // copyTree copies srcDir to tar file dstDir, ignoring skipFiles. 239 func copyTree(tw *tar.Writer, dstDir, srcDir string) error { 240 entries, err := ioutil.ReadDir(srcDir) 241 if err != nil { 242 return fmt.Errorf("unable to read dir %v: %v", srcDir, err) 243 } 244 for _, entry := range entries { 245 n := entry.Name() 246 if skipFiles[n] { 247 continue 248 } 249 s := filepath.Join(srcDir, n) 250 d := filepath.Join(dstDir, n) 251 if entry.IsDir() { 252 if err := copyTree(tw, d, s); err != nil { 253 return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err) 254 } 255 continue 256 } 257 if err := copyFile(tw, d, s); err != nil { 258 return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err) 259 } 260 } 261 return nil 262 } 263 264 // copyFile copies src to tar file dst. 265 func copyFile(tw *tar.Writer, dst, src string) error { 266 s, err := os.Open(src) 267 if err != nil { 268 return fmt.Errorf("unable to open %v: %v", src, err) 269 } 270 defer s.Close() 271 fi, err := s.Stat() 272 if err != nil { 273 return fmt.Errorf("unable to stat %v: %v", src, err) 274 } 275 276 hdr, err := tar.FileInfoHeader(fi, dst) 277 if err != nil { 278 return fmt.Errorf("unable to create tar header for %v: %v", dst, err) 279 } 280 hdr.Name = dst 281 if err := tw.WriteHeader(hdr); err != nil { 282 return fmt.Errorf("unable to write header for %v: %v", dst, err) 283 } 284 _, err = io.Copy(tw, s) 285 if err != nil { 286 return fmt.Errorf("unable to copy %v to %v: %v", src, dst, err) 287 } 288 return nil 289 } 290 291 // checkMain verifies that there is a single "main" function. 292 // It also returns a list of all Go source files in the app. 293 func checkMain(ctxt *build.Context) (bool, []string, error) { 294 pkg, err := ctxt.ImportDir(*rootDir, 0) 295 if err != nil { 296 return false, nil, fmt.Errorf("unable to analyze source: %v", err) 297 } 298 if !pkg.IsCommand() { 299 errorf("Your app's package needs to be changed from %q to \"main\".\n", pkg.Name) 300 } 301 // Search for a "func main" 302 var hasMain bool 303 var appFiles []string 304 for _, f := range pkg.GoFiles { 305 n := filepath.Join(*rootDir, f) 306 appFiles = append(appFiles, n) 307 if hasMain, err = readFile(n); err != nil { 308 return false, nil, fmt.Errorf("error parsing %q: %v", n, err) 309 } 310 } 311 return hasMain, appFiles, nil 312 } 313 314 // isMain returns whether the given function declaration is a main function. 315 // Such a function must be called "main", not have a receiver, and have no arguments or return types. 316 func isMain(f *ast.FuncDecl) bool { 317 ft := f.Type 318 return f.Name.Name == "main" && f.Recv == nil && ft.Params.NumFields() == 0 && ft.Results.NumFields() == 0 319 } 320 321 // readFile reads and parses the Go source code file and returns whether it has a main function. 322 func readFile(filename string) (hasMain bool, err error) { 323 var src []byte 324 src, err = ioutil.ReadFile(filename) 325 if err != nil { 326 return 327 } 328 fset := token.NewFileSet() 329 file, err := parser.ParseFile(fset, filename, src, 0) 330 for _, decl := range file.Decls { 331 funcDecl, ok := decl.(*ast.FuncDecl) 332 if !ok { 333 continue 334 } 335 if !isMain(funcDecl) { 336 continue 337 } 338 hasMain = true 339 break 340 } 341 return 342 }