github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/make.go (about) 1 // +build ignore 2 3 /* 4 Copyright 2013 Google Inc. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 // This program builds Camlistore. 20 // 21 // $ go run make.go 22 // 23 // See the BUILDING file. 24 // 25 // The output binaries go into the ./bin/ directory (under the 26 // Camlistore root, where make.go is) 27 package main 28 29 import ( 30 "archive/zip" 31 "bytes" 32 "flag" 33 "fmt" 34 "io" 35 "io/ioutil" 36 "log" 37 "os" 38 "os/exec" 39 pathpkg "path" 40 "path/filepath" 41 "regexp" 42 "runtime" 43 "strconv" 44 "strings" 45 "time" 46 ) 47 48 var haveSQLite = checkHaveSQLite() 49 50 var ( 51 embedResources = flag.Bool("embed_static", true, "Whether to embed resources needed by the UI such as images, css, and javascript.") 52 sqlFlag = flag.String("sqlite", "auto", "Whether you want SQLite in your build: yes, no, or auto.") 53 all = flag.Bool("all", false, "Force rebuild of everything (go install -a)") 54 race = flag.Bool("race", false, "Build race-detector version of binaries (they will run slowly)") 55 verbose = flag.Bool("v", false, "Verbose mode") 56 targets = flag.String("targets", "", "Optional comma-separated list of targets (i.e go packages) to build and install. Empty means all. Example: camlistore.org/server/camlistored,camlistore.org/cmd/camput") 57 quiet = flag.Bool("quiet", false, "Don't print anything unless there's a failure.") 58 onlysync = flag.Bool("onlysync", false, "Only populate the temporary source/build tree and output its full path. It is meant to prepare the environment for running the full test suite with 'devcam test'.") 59 // TODO(mpl): looks like ifModsSince is not used anywhere? 60 ifModsSince = flag.Int64("if_mods_since", 0, "If non-zero return immediately without building if there aren't any filesystem modifications past this time (in unix seconds)") 61 buildARCH = flag.String("arch", runtime.GOARCH, "Architecture to build for.") 62 buildOS = flag.String("os", runtime.GOOS, "Operating system to build for.") 63 ) 64 65 var ( 66 // buildGoPath becomes our child "go" processes' GOPATH environment variable 67 buildGoPath string 68 // Our temporary source tree root and build dir, i.e: buildGoPath + "src/camlistore.org" 69 buildSrcDir string 70 // files mirrored from camRoot to buildSrcDir 71 rxMirrored = regexp.MustCompile(`^([a-zA-Z0-9\-\_]+\.(?:camli|css|err|gif|go|gpg|html|ico|jpg|js|json|min\.js|mp3|png|svg))$`) 72 ) 73 74 func main() { 75 log.SetFlags(0) 76 flag.Parse() 77 78 camRoot, err := os.Getwd() 79 if err != nil { 80 log.Fatalf("Failed to get current directory: %v", err) 81 } 82 verifyCamlistoreRoot(camRoot) 83 84 cross := runtime.GOOS != *buildOS || runtime.GOARCH != *buildARCH 85 var sql bool 86 if *sqlFlag == "auto" { 87 sql = !cross && haveSQLite 88 } else { 89 sql, err = strconv.ParseBool(*sqlFlag) 90 if err != nil { 91 log.Fatalf("Bad boolean --sql flag %q", *sqlFlag) 92 } 93 } 94 95 if cross && sql { 96 log.Fatalf("SQLite isn't available when cross-compiling to another OS. Set --sqlite=false.") 97 } 98 if sql && !haveSQLite { 99 log.Printf("SQLite not found. Either install it, or run make.go with --sqlite=false See https://code.google.com/p/camlistore/wiki/SQLite") 100 switch runtime.GOOS { 101 case "darwin": 102 log.Printf("On OS X, run 'brew install sqlite3 pkg-config'. Get brew from http://mxcl.github.io/homebrew/") 103 case "linux": 104 log.Printf("On Linux, run 'sudo apt-get install libsqlite3-dev' or equivalent.") 105 case "windows": 106 log.Printf("SQLite is not easy on windows. Please see http://camlistore.org/docs/server-config#windows") 107 } 108 os.Exit(2) 109 } 110 111 buildBaseDir := "build-gopath" 112 if !sql { 113 buildBaseDir += "-nosqlite" 114 } 115 116 buildGoPath = filepath.Join(camRoot, "tmp", buildBaseDir) 117 binDir := filepath.Join(camRoot, "bin") 118 buildSrcDir = filepath.Join(buildGoPath, "src", "camlistore.org") 119 120 if err := os.MkdirAll(buildSrcDir, 0755); err != nil { 121 log.Fatal(err) 122 } 123 124 version := getVersion(camRoot) 125 126 if *verbose { 127 log.Printf("Camlistore version = %s", version) 128 log.Printf("SQLite included: %v", sql) 129 log.Printf("Temporary source: %s", buildSrcDir) 130 log.Printf("Output binaries: %s", binDir) 131 } 132 133 // TODO(mpl): main is getting long. We could probably move all the mirroring 134 // dance to its own func. 135 // We copy all *.go files from camRoot's goDirs to buildSrcDir. 136 goDirs := []string{"cmd", "pkg", "dev", "server/camlistored", "third_party"} 137 if *onlysync { 138 goDirs = append(goDirs, "server/appengine", "config") 139 } 140 // Copy files we do want in our mirrored GOPATH. This has the side effect of 141 // populating wantDestFile, populated by mirrorFile. 142 var latestSrcMod time.Time 143 for _, dir := range goDirs { 144 oriPath := filepath.Join(camRoot, filepath.FromSlash(dir)) 145 dstPath := buildSrcPath(dir) 146 if maxMod, err := mirrorDir(oriPath, dstPath, mirrorOpts{sqlite: sql}); err != nil { 147 log.Fatalf("Error while mirroring %s to %s: %v", oriPath, dstPath, err) 148 } else { 149 if maxMod.After(latestSrcMod) { 150 latestSrcMod = maxMod 151 } 152 } 153 } 154 155 verifyGoVersion() 156 157 if *onlysync { 158 mirrorFile("make.go", filepath.Join(buildSrcDir, "make.go")) 159 deleteUnwantedOldMirrorFiles(buildSrcDir, true) 160 fmt.Println(buildGoPath) 161 return 162 } 163 164 buildAll := true 165 targs := []string{ 166 "camlistore.org/dev/devcam", 167 "camlistore.org/cmd/camget", 168 "camlistore.org/cmd/camput", 169 "camlistore.org/cmd/camtool", 170 "camlistore.org/server/camlistored", 171 } 172 if *targets != "" { 173 if t := strings.Split(*targets, ","); len(t) != 0 { 174 targs = t 175 buildAll = false 176 } 177 } 178 179 withCamlistored := stringListContains(targs, "camlistore.org/server/camlistored") 180 if *embedResources && withCamlistored { 181 if *verbose { 182 log.Printf("Embedding resources...") 183 } 184 closureEmbed := buildSrcPath("server/camlistored/ui/closure/z_data.go") 185 closureSrcDir := filepath.Join(camRoot, filepath.FromSlash("third_party/closure/lib")) 186 err := embedClosure(closureSrcDir, closureEmbed) 187 if err != nil { 188 log.Fatal(err) 189 } 190 wantDestFile[closureEmbed] = true 191 if err = buildGenfileembed(); err != nil { 192 log.Fatal(err) 193 } 194 if err = genEmbeds(); err != nil { 195 log.Fatal(err) 196 } 197 } 198 199 deleteUnwantedOldMirrorFiles(buildSrcDir, withCamlistored) 200 201 tags := "" 202 if sql { 203 tags = "with_sqlite" 204 } 205 baseArgs := []string{"install", "-v"} 206 if *all { 207 baseArgs = append(baseArgs, "-a") 208 } 209 if *race { 210 baseArgs = append(baseArgs, "-race") 211 } 212 baseArgs = append(baseArgs, 213 "--ldflags=-X camlistore.org/pkg/buildinfo.GitInfo "+version, 214 "--tags="+tags) 215 216 if buildAll { 217 switch *buildOS { 218 case "linux", "darwin": 219 targs = append(targs, "camlistore.org/cmd/cammount") 220 } 221 } 222 223 // First install command: build just the final binaries, installed to a GOBIN 224 // under <camlistore_root>/bin: 225 args := append(baseArgs, targs...) 226 227 if buildAll { 228 args = append(args, 229 "camlistore.org/pkg/...", 230 "camlistore.org/server/...", 231 "camlistore.org/third_party/...", 232 ) 233 } 234 235 cmd := exec.Command("go", args...) 236 cmd.Env = append(cleanGoEnv(), 237 "GOPATH="+buildGoPath, 238 ) 239 var output bytes.Buffer 240 if *quiet { 241 cmd.Stdout = &output 242 cmd.Stderr = &output 243 } else { 244 cmd.Stdout = os.Stdout 245 cmd.Stderr = os.Stderr 246 } 247 if *verbose { 248 log.Printf("Running go install of main binaries with args %s", cmd.Args) 249 } 250 if err := cmd.Run(); err != nil { 251 log.Fatalf("Error building main binaries: %v\n%s", err, output.String()) 252 } 253 254 // Copy the binaries from $CAMROOT/tmp/build-gopath-foo/bin to $CAMROOT/bin. 255 // This is necessary (instead of just using GOBIN environment variable) so 256 // each tmp/build-gopath-* has its own binary modtimes for its own build tags. 257 // Otherwise switching sqlite true<->false doesn't necessarily cause a rebuild. 258 // See camlistore.org/issue/229 259 for _, targ := range targs { 260 src := exeName(filepath.Join(actualBinDir(filepath.Join(buildGoPath, "bin")), pathpkg.Base(targ))) 261 dst := exeName(filepath.Join(actualBinDir(binDir), pathpkg.Base(targ))) 262 if err := mirrorFile(src, dst); err != nil { 263 log.Fatalf("Error copying %s to %s: %v", src, dst, err) 264 } 265 } 266 267 if !*quiet { 268 log.Printf("Success. Binaries are in %s", actualBinDir(binDir)) 269 } 270 } 271 272 func actualBinDir(dir string) string { 273 if *buildARCH == runtime.GOARCH && *buildOS == runtime.GOOS { 274 return dir 275 } 276 return filepath.Join(dir, *buildOS+"_"+*buildARCH) 277 } 278 279 // Create an environment variable of the form key=value. 280 func envPair(key, value string) string { 281 return fmt.Sprintf("%s=%s", key, value) 282 } 283 284 // cleanGoEnv returns a copy of the current environment with GOPATH and GOBIN removed. 285 // it also sets GOOS and GOARCH as needed when cross-compiling. 286 func cleanGoEnv() (clean []string) { 287 for _, env := range os.Environ() { 288 if strings.HasPrefix(env, "GOPATH=") || strings.HasPrefix(env, "GOBIN=") { 289 continue 290 } 291 // We skip these two as well, otherwise they'd take precedence over the 292 // ones appended below. 293 if *buildOS != runtime.GOOS && strings.HasPrefix(env, "GOOS=") { 294 continue 295 } 296 if *buildARCH != runtime.GOARCH && strings.HasPrefix(env, "GOARCH=") { 297 continue 298 } 299 clean = append(clean, env) 300 } 301 if *buildOS != runtime.GOOS { 302 clean = append(clean, envPair("GOOS", *buildOS)) 303 } 304 if *buildARCH != runtime.GOARCH { 305 clean = append(clean, envPair("GOARCH", *buildARCH)) 306 } 307 return 308 } 309 310 // setEnv sets the given key & value in the provided environment. 311 // Each value in the env list should be of the form key=value. 312 func setEnv(env []string, key, value string) []string { 313 for i, s := range env { 314 if strings.HasPrefix(s, fmt.Sprintf("%s=", key)) { 315 env[i] = envPair(key, value) 316 return env 317 } 318 } 319 env = append(env, envPair(key, value)) 320 return env 321 } 322 323 func stringListContains(strs []string, str string) bool { 324 for _, s := range strs { 325 if s == str { 326 return true 327 } 328 } 329 return false 330 } 331 332 // buildSrcPath returns the full path concatenation 333 // of buildSrcDir with fromSrc. 334 func buildSrcPath(fromSrc string) string { 335 return filepath.Join(buildSrcDir, filepath.FromSlash(fromSrc)) 336 } 337 338 // genEmbeds generates from the static resources the zembed.*.go 339 // files that will allow for these resources to be included in 340 // the camlistored binary. 341 // It also populates wantDestFile with those files so they're 342 // kept in between runs. 343 func genEmbeds() error { 344 cmdName := filepath.Join(buildGoPath, "bin", "genfileembed") 345 uiEmbeds := buildSrcPath("server/camlistored/ui") 346 serverEmbeds := buildSrcPath("pkg/server") 347 reactEmbeds := buildSrcPath("third_party/react") 348 glitchEmbeds := buildSrcPath("third_party/glitch") 349 for _, embeds := range []string{uiEmbeds, serverEmbeds, reactEmbeds, glitchEmbeds} { 350 args := []string{embeds} 351 cmd := exec.Command(cmdName, args...) 352 cmd.Env = append(cleanGoEnv(), 353 "GOPATH="+buildGoPath, 354 ) 355 cmd.Stdout = os.Stdout 356 cmd.Stderr = os.Stderr 357 if *verbose { 358 log.Printf("Running %s %s", cmdName, embeds) 359 } 360 if err := cmd.Run(); err != nil { 361 return fmt.Errorf("Error running %s %s: %v", cmdName, embeds, err) 362 } 363 // We mark all the zembeds in builddir as wanted, so that we do not 364 // have to regen them next time, unless they need updating. 365 f, err := os.Open(embeds) 366 if err != nil { 367 return err 368 } 369 defer f.Close() 370 names, err := f.Readdirnames(-1) 371 if err != nil { 372 return err 373 } 374 for _, v := range names { 375 if strings.HasPrefix(v, "zembed_") { 376 wantDestFile[filepath.Join(embeds, v)] = true 377 } 378 } 379 } 380 return nil 381 } 382 383 func buildGenfileembed() error { 384 args := []string{"install", "-v"} 385 if *all { 386 args = append(args, "-a") 387 } 388 args = append(args, 389 filepath.FromSlash("camlistore.org/pkg/fileembed/genfileembed"), 390 ) 391 cmd := exec.Command("go", args...) 392 393 // We don't even need to set GOBIN as it defaults to $GOPATH/bin 394 // and that is where we want genfileembed to go. 395 // Here we replace the GOOS and GOARCH valuesfrom the env with the host OS, 396 // to support cross-compiling. 397 cmd.Env = cleanGoEnv() 398 cmd.Env = setEnv(cmd.Env, "GOPATH", buildGoPath) 399 cmd.Env = setEnv(cmd.Env, "GOOS", runtime.GOOS) 400 cmd.Env = setEnv(cmd.Env, "GOARCH", runtime.GOARCH) 401 402 cmd.Stdout = os.Stdout 403 cmd.Stderr = os.Stderr 404 if *verbose { 405 log.Printf("Running go with args %s", args) 406 } 407 if err := cmd.Run(); err != nil { 408 return fmt.Errorf("Error building genfileembed: %v", err) 409 } 410 if *verbose { 411 log.Printf("genfileembed installed in %s", filepath.Join(buildGoPath, "bin")) 412 } 413 return nil 414 } 415 416 // getVersion returns the version of Camlistore. Either from a VERSION file at the root, 417 // or from git. 418 func getVersion(camRoot string) string { 419 slurp, err := ioutil.ReadFile(filepath.Join(camRoot, "VERSION")) 420 if err == nil { 421 return strings.TrimSpace(string(slurp)) 422 } 423 return gitVersion(camRoot) 424 } 425 426 var gitVersionRx = regexp.MustCompile(`\b\d\d\d\d-\d\d-\d\d-[0-9a-f]{7,7}\b`) 427 428 // gitVersion returns the git version of the git repo at camRoot as a 429 // string of the form "yyyy-mm-dd-xxxxxxx", with an optional trailing 430 // '+' if there are any local uncomitted modifications to the tree. 431 func gitVersion(camRoot string) string { 432 cmd := exec.Command("git", "rev-list", "--max-count=1", "--pretty=format:'%ad-%h'", "--date=short", "HEAD") 433 cmd.Dir = camRoot 434 out, err := cmd.Output() 435 if err != nil { 436 log.Fatalf("Error running git rev-list in %s: %v", camRoot, err) 437 } 438 v := strings.TrimSpace(string(out)) 439 if m := gitVersionRx.FindStringSubmatch(v); m != nil { 440 v = m[0] 441 } else { 442 panic("Failed to find git version in " + v) 443 } 444 cmd = exec.Command("git", "diff", "--exit-code") 445 cmd.Dir = camRoot 446 if err := cmd.Run(); err != nil { 447 v += "+" 448 } 449 return v 450 } 451 452 // verifyCamlistoreRoot crashes if dir isn't the Camlistore root directory. 453 func verifyCamlistoreRoot(dir string) { 454 testFile := filepath.Join(dir, "pkg", "blob", "ref.go") 455 if _, err := os.Stat(testFile); err != nil { 456 log.Fatalf("make.go must be run from the Camlistore src root directory (where make.go is). Current working directory is %s", dir) 457 } 458 } 459 460 func verifyGoVersion() { 461 _, err := exec.LookPath("go") 462 if err != nil { 463 log.Fatalf("Go doesn't appeared to be installed ('go' isn't in your PATH). Install Go 1.1 or newer.") 464 } 465 out, err := exec.Command("go", "version").Output() 466 if err != nil { 467 log.Fatalf("Error checking Go version with the 'go' command: %v", err) 468 } 469 fields := strings.Fields(string(out)) 470 if len(fields) < 3 || !strings.HasPrefix(string(out), "go version ") { 471 log.Fatalf("Unexpected output while checking 'go version': %q", out) 472 } 473 version := fields[2] 474 switch version { 475 case "go1", "go1.0.1", "go1.0.2", "go1.0.3": 476 log.Fatalf("Your version of Go (%s) is too old. Camlistore requires Go 1.1 or later.", version) 477 } 478 } 479 480 type mirrorOpts struct { 481 sqlite bool // want sqlite package? 482 } 483 484 func mirrorDir(src, dst string, opts mirrorOpts) (maxMod time.Time, err error) { 485 err = filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { 486 if err != nil { 487 return err 488 } 489 base := fi.Name() 490 if fi.IsDir() { 491 if !opts.sqlite && strings.Contains(path, "mattn") && strings.Contains(path, "go-sqlite3") { 492 return filepath.SkipDir 493 } 494 return nil 495 } 496 if strings.HasPrefix(base, ".#") || !rxMirrored.MatchString(base) { 497 return nil 498 } 499 suffix, err := filepath.Rel(src, path) 500 if err != nil { 501 return fmt.Errorf("Failed to find Rel(%q, %q): %v", src, path, err) 502 } 503 if t := fi.ModTime(); t.After(maxMod) { 504 maxMod = t 505 } 506 return mirrorFile(path, filepath.Join(dst, suffix)) 507 }) 508 return 509 } 510 511 var wantDestFile = make(map[string]bool) // full dest filename => true 512 513 func isExecMode(mode os.FileMode) bool { 514 return (mode & 0111) != 0 515 } 516 517 func mirrorFile(src, dst string) error { 518 wantDestFile[dst] = true 519 sfi, err := os.Stat(src) 520 if err != nil { 521 return err 522 } 523 if sfi.Mode()&os.ModeType != 0 { 524 log.Fatalf("mirrorFile can't deal with non-regular file %s", src) 525 } 526 dfi, err := os.Stat(dst) 527 if err == nil && 528 isExecMode(sfi.Mode()) == isExecMode(dfi.Mode()) && 529 (dfi.Mode()&os.ModeType == 0) && 530 dfi.Size() == sfi.Size() && 531 dfi.ModTime().Unix() == sfi.ModTime().Unix() { 532 // Seems to not be modified. 533 return nil 534 } 535 536 dstDir := filepath.Dir(dst) 537 if err := os.MkdirAll(dstDir, 0755); err != nil { 538 return err 539 } 540 541 df, err := os.Create(dst) 542 if err != nil { 543 return err 544 } 545 sf, err := os.Open(src) 546 if err != nil { 547 return err 548 } 549 defer sf.Close() 550 551 n, err := io.Copy(df, sf) 552 if err == nil && n != sfi.Size() { 553 err = fmt.Errorf("copied wrong size for %s -> %s: copied %d; want %d", src, dst, n, sfi.Size()) 554 } 555 cerr := df.Close() 556 if err == nil { 557 err = cerr 558 } 559 if err == nil { 560 err = os.Chmod(dst, sfi.Mode()) 561 } 562 if err == nil { 563 err = os.Chtimes(dst, sfi.ModTime(), sfi.ModTime()) 564 } 565 return err 566 } 567 568 func deleteUnwantedOldMirrorFiles(dir string, withCamlistored bool) { 569 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 570 if err != nil { 571 log.Fatalf("Error stating while cleaning %s: %v", path, err) 572 } 573 if fi.IsDir() { 574 return nil 575 } 576 if !wantDestFile[path] { 577 if !withCamlistored && (strings.Contains(path, "zembed_") || strings.Contains(path, "z_data.go")) { 578 // If we're not building the camlistored binary, 579 // no need to clean up the embedded Closure, JS, 580 // CSS, HTML, etc. Doing so would just mean we'd 581 // have to put it back into place later. 582 return nil 583 } 584 if !*quiet { 585 log.Printf("Deleting old file from temp build dir: %s", path) 586 } 587 return os.Remove(path) 588 } 589 return nil 590 }) 591 } 592 593 func checkHaveSQLite() bool { 594 if runtime.GOOS == "windows" { 595 // TODO: Find some other non-pkg-config way to test, like 596 // just compiling a small Go program that sees whether 597 // it's available. 598 // 599 // For now: 600 return false 601 } 602 _, err := exec.LookPath("pkg-config") 603 if err != nil { 604 return false 605 } 606 out, err := exec.Command("pkg-config", "--libs", "sqlite3").Output() 607 if err != nil && err.Error() == "exit status 1" { 608 // This is sloppy (comparing against a string), but 609 // doing it correctly requires using multiple *.go 610 // files to portably get the OS-syscall bits, and I 611 // want to keep make.go a single file. 612 return false 613 } 614 if err != nil { 615 log.Fatalf("Can't determine whether sqlite3 is available, and where. pkg-config error was: %v, %s", err, out) 616 } 617 return strings.TrimSpace(string(out)) != "" 618 } 619 620 func embedClosure(closureDir, embedFile string) error { 621 if _, err := os.Stat(closureDir); err != nil { 622 return fmt.Errorf("Could not stat %v: %v", closureDir, err) 623 } 624 625 // first, zip it 626 var zipbuf bytes.Buffer 627 var zipdest io.Writer = &zipbuf 628 if os.Getenv("CAMLI_WRITE_TMP_ZIP") != "" { 629 f, _ := os.Create("/tmp/camli-closure.zip") 630 zipdest = io.MultiWriter(zipdest, f) 631 defer f.Close() 632 } 633 var modTime time.Time 634 w := zip.NewWriter(zipdest) 635 err := filepath.Walk(closureDir, func(path string, fi os.FileInfo, err error) error { 636 if err != nil { 637 return err 638 } 639 suffix, err := filepath.Rel(closureDir, path) 640 if err != nil { 641 return fmt.Errorf("Failed to find Rel(%q, %q): %v", closureDir, path, err) 642 } 643 if fi.IsDir() { 644 return nil 645 } 646 if mt := fi.ModTime(); mt.After(modTime) { 647 modTime = mt 648 } 649 b, err := ioutil.ReadFile(path) 650 if err != nil { 651 return err 652 } 653 f, err := w.Create(filepath.ToSlash(suffix)) 654 if err != nil { 655 log.Fatal(err) 656 } 657 _, err = f.Write(b) 658 return err 659 }) 660 if err != nil { 661 return err 662 } 663 err = w.Close() 664 if err != nil { 665 return err 666 } 667 668 // then embed it as a quoted string 669 var qb bytes.Buffer 670 fmt.Fprint(&qb, "package closure\n\n") 671 fmt.Fprint(&qb, "import \"time\"\n\n") 672 fmt.Fprint(&qb, "func init() {\n") 673 fmt.Fprintf(&qb, "\tZipModTime = time.Unix(%d, 0)\n", modTime.Unix()) 674 fmt.Fprint(&qb, "\tZipData = ") 675 quote(&qb, zipbuf.Bytes()) 676 fmt.Fprint(&qb, "\n}\n") 677 678 // and write to a .go file 679 // TODO(mpl): do not regenerate the whole zip file if the modtime 680 // of the z_data.go file is greater than the modtime of all the closure *.js files. 681 if err := writeFileIfDifferent(embedFile, qb.Bytes()); err != nil { 682 return err 683 } 684 return nil 685 686 } 687 688 func writeFileIfDifferent(filename string, contents []byte) error { 689 fi, err := os.Stat(filename) 690 if err == nil && fi.Size() == int64(len(contents)) && contentsEqual(filename, contents) { 691 return nil 692 } 693 return ioutil.WriteFile(filename, contents, 0644) 694 } 695 696 func contentsEqual(filename string, contents []byte) bool { 697 got, err := ioutil.ReadFile(filename) 698 if err != nil { 699 return false 700 } 701 return bytes.Equal(got, contents) 702 } 703 704 // quote escapes and quotes the bytes from bs and writes 705 // them to dest. 706 func quote(dest *bytes.Buffer, bs []byte) { 707 dest.WriteByte('"') 708 for _, b := range bs { 709 if b == '\n' { 710 dest.WriteString(`\n`) 711 continue 712 } 713 if b == '\\' { 714 dest.WriteString(`\\`) 715 continue 716 } 717 if b == '"' { 718 dest.WriteString(`\"`) 719 continue 720 } 721 if (b >= 32 && b <= 126) || b == '\t' { 722 dest.WriteByte(b) 723 continue 724 } 725 fmt.Fprintf(dest, "\\x%02x", b) 726 } 727 dest.WriteByte('"') 728 } 729 730 func exeName(s string) string { 731 if *buildOS == "windows" { 732 return s + ".exe" 733 } 734 return s 735 }