2013-06-10 22:55:17 +00:00
/ *
Copyright 2013 Google Inc .
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 .
* /
// This program builds Camlistore.
//
2013-06-11 11:19:46 +00:00
// $ go run make.go
//
// See the BUILDING file.
//
2013-06-10 22:55:17 +00:00
// The output binaries go into the ./bin/ directory (under the
// Camlistore root, where make.go is)
package main
import (
2013-06-18 20:02:03 +00:00
"archive/zip"
"bytes"
2013-06-10 22:55:17 +00:00
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
2013-06-20 13:49:34 +00:00
"regexp"
2013-06-10 22:55:17 +00:00
"runtime"
"strings"
2013-06-19 06:14:36 +00:00
"time"
2013-06-10 22:55:17 +00:00
)
2013-06-10 23:18:29 +00:00
var (
2013-06-18 20:02:03 +00:00
embedResources = flag . Bool ( "embed_static" , true , "Whether to embed the closure library." )
wantSQLite = flag . Bool ( "sqlite" , true , "Whether you want SQLite in your build. If you don't have any other database, you generally do." )
all = flag . Bool ( "all" , false , "Force rebuild of everything (go install -a)" )
verbose = flag . Bool ( "v" , false , "Verbose mode" )
2013-06-24 21:03:46 +00:00
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" )
2013-06-29 20:52:01 +00:00
quiet = flag . Bool ( "quiet" , false , "Don't print anything unless there's a failure." )
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)" )
2013-07-05 17:41:07 +00:00
buildOS = flag . String ( "os" , runtime . GOOS , "Operating system to build for." )
2013-06-10 23:18:29 +00:00
)
2013-06-10 22:55:17 +00:00
2013-06-20 13:49:34 +00:00
var (
// buildGoPath becomes our child "go" processes' GOPATH environment variable
buildGoPath string
// Our temporary source tree root and build dir, i.e: buildGoPath + "src/camlistore.org"
buildSrcDir string
// files mirrored from camRoot to buildSrcDir
2013-06-26 17:24:36 +00:00
rxMirrored = regexp . MustCompile ( ` ^([a-zA-Z0-9\-\_]+\.(?:go|html|js|css|png|jpg|gif|ico))$ ` )
2013-06-20 13:49:34 +00:00
)
2013-06-10 22:55:17 +00:00
func main ( ) {
log . SetFlags ( 0 )
flag . Parse ( )
camRoot , err := os . Getwd ( )
if err != nil {
log . Fatalf ( "Failed to get current directory: %v" , err )
}
verifyCamlistoreRoot ( camRoot )
2013-07-05 17:41:07 +00:00
if runtime . GOOS != * buildOS {
if * wantSQLite {
log . Fatalf ( "SQLite isn't available when cross-compiling to another OS. Set --sqlite=false." )
}
// TODO(bradfitz): we can fix this one, though, by
// building genfileembed with the GOOS of the host
// instead:
if * embedResources {
log . Fatalf ( "Due to a bug, can't currently cross-compile and also embed resources. For now, set --embed_static=false" )
}
}
2013-06-29 20:52:01 +00:00
sql := * wantSQLite && haveSQLite ( )
2013-06-10 23:22:28 +00:00
buildBaseDir := "build-gopath"
if ! sql {
buildBaseDir += "-nosqlite"
}
2013-06-20 13:49:34 +00:00
buildGoPath = filepath . Join ( camRoot , "tmp" , buildBaseDir )
2013-06-10 22:55:17 +00:00
binDir := filepath . Join ( camRoot , "bin" )
2013-06-20 13:49:34 +00:00
buildSrcDir = filepath . Join ( buildGoPath , "src" , "camlistore.org" )
2013-06-10 22:55:17 +00:00
2013-06-20 13:49:34 +00:00
if err := os . MkdirAll ( buildSrcDir , 0755 ) ; err != nil {
2013-06-10 22:55:17 +00:00
log . Fatal ( err )
}
version := getVersion ( camRoot )
2013-06-10 23:22:28 +00:00
if * verbose {
log . Printf ( "Camlistore version = %s" , version )
log . Printf ( "SQLite available: %v" , sql )
2013-06-20 13:49:34 +00:00
log . Printf ( "Temporary source: %s" , buildSrcDir )
2013-06-10 23:22:28 +00:00
log . Printf ( "Output binaries: %s" , binDir )
}
2013-06-10 22:55:17 +00:00
if ! sql && * wantSQLite {
log . Printf ( "SQLite not found. Either install it, or run make.go with --sqlite=false" )
switch runtime . GOOS {
case "darwin" :
2013-07-06 21:52:08 +00:00
log . Printf ( "On OS X, run 'brew install sqlite3 pkg-config'. Get brew from http://mxcl.github.io/homebrew/" )
2013-06-10 22:55:17 +00:00
case "linux" :
log . Printf ( "On Linux, run 'sudo apt-get install libsqlite3-dev' or equivalent." )
case "windows" :
log . Printf ( "On Windows, .... click stuff? TODO: fill this in" )
}
os . Exit ( 2 )
}
2013-06-20 13:49:34 +00:00
// We copy all *.go files from camRoot's goDirs to buildSrcDir.
goDirs := [ ] string { "cmd" , "pkg" , "server/camlistored" , "third_party" }
2013-06-10 22:55:17 +00:00
// Copy files we do want in our mirrored GOPATH. This has the side effect of
// populating wantDestFile, populated by mirrorFile.
2013-06-29 20:52:01 +00:00
var latestSrcMod time . Time
2013-06-10 22:55:17 +00:00
for _ , dir := range goDirs {
2013-06-20 13:49:34 +00:00
oriPath := filepath . Join ( camRoot , filepath . FromSlash ( dir ) )
dstPath := buildSrcPath ( dir )
2013-06-29 20:52:01 +00:00
if maxMod , err := mirrorDir ( oriPath , dstPath ) ; err != nil {
2013-06-20 13:49:34 +00:00
log . Fatalf ( "Error while mirroring %s to %s: %v" , oriPath , dstPath , err )
2013-06-29 20:52:01 +00:00
} else {
if maxMod . After ( latestSrcMod ) {
latestSrcMod = maxMod
}
2013-06-10 22:55:17 +00:00
}
}
2013-06-29 20:52:01 +00:00
verifyGoVersion ( )
2013-06-18 20:02:03 +00:00
if * embedResources {
2013-06-29 20:52:01 +00:00
if * verbose {
log . Printf ( "Embedding resources..." )
}
closureEmbed := buildSrcPath ( "server/camlistored/ui/closure/z_data.go" )
2013-06-20 13:49:34 +00:00
closureSrcDir := filepath . Join ( camRoot , filepath . FromSlash ( "third_party/closure/lib" ) )
2013-06-18 20:02:03 +00:00
err := embedClosure ( closureSrcDir , closureEmbed )
if err != nil {
log . Fatal ( err )
}
2013-06-18 21:45:40 +00:00
wantDestFile [ closureEmbed ] = true
2013-06-20 13:49:34 +00:00
if err = buildGenfileembed ( ) ; err != nil {
log . Fatal ( err )
}
if err = genEmbeds ( ) ; err != nil {
2013-06-18 23:04:27 +00:00
log . Fatal ( err )
}
2013-06-18 20:02:03 +00:00
}
2013-06-20 13:49:34 +00:00
deleteUnwantedOldMirrorFiles ( buildSrcDir )
2013-06-18 21:45:40 +00:00
2013-06-10 22:55:17 +00:00
tags := ""
if sql && * wantSQLite {
tags = "with_sqlite"
}
2013-06-22 03:18:02 +00:00
baseArgs := [ ] string { "install" , "-v" }
2013-06-10 23:18:29 +00:00
if * all {
2013-06-22 03:18:02 +00:00
baseArgs = append ( baseArgs , "-a" )
2013-06-10 23:18:29 +00:00
}
2013-06-22 03:18:02 +00:00
baseArgs = append ( baseArgs ,
2013-06-10 22:55:17 +00:00
"--ldflags=-X camlistore.org/pkg/buildinfo.GitInfo " + version ,
2013-06-22 03:18:02 +00:00
"--tags=" + tags )
// First install command: build just the final binaries, installed to a GOBIN
// under <camlistore_root>/bin:
2013-06-24 21:03:46 +00:00
buildAll := true
targs := [ ] string {
2013-06-10 22:55:17 +00:00
"camlistore.org/cmd/camget" ,
"camlistore.org/cmd/camput" ,
"camlistore.org/cmd/camtool" ,
2013-06-12 15:55:41 +00:00
"camlistore.org/server/camlistored" ,
2013-06-24 21:03:46 +00:00
}
if * targets != "" {
if t := strings . Split ( * targets , "," ) ; len ( t ) != 0 {
targs = t
buildAll = false
}
}
args := append ( baseArgs , targs ... )
if buildAll {
2013-07-05 17:41:07 +00:00
switch * buildOS {
2013-06-24 21:03:46 +00:00
case "linux" , "darwin" :
args = append ( args , "camlistore.org/cmd/cammount" )
}
2013-06-10 22:55:17 +00:00
}
2013-06-10 23:18:29 +00:00
cmd := exec . Command ( "go" , args ... )
2013-06-18 22:21:56 +00:00
cmd . Env = append ( cleanGoEnv ( ) ,
2013-06-20 13:49:34 +00:00
"GOPATH=" + buildGoPath ,
2013-06-18 22:21:56 +00:00
"GOBIN=" + binDir ,
)
2013-06-29 20:52:01 +00:00
var output bytes . Buffer
if * quiet {
cmd . Stdout = & output
cmd . Stderr = & output
} else {
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
}
2013-06-10 23:22:28 +00:00
if * verbose {
2013-07-05 17:41:07 +00:00
log . Printf ( "Running go install of main binaries with args %s" , cmd . Args )
2013-06-22 03:18:02 +00:00
}
if err := cmd . Run ( ) ; err != nil {
2013-07-04 02:44:57 +00:00
log . Fatalf ( "Error building main binaries: %v\n%s" , err , output . String ( ) )
2013-06-10 23:22:28 +00:00
}
2013-06-29 20:52:01 +00:00
if buildAll {
// Now do another build, but including everything, just to make
// sure everything compiles. But if there are any binaries (package main) in here,
// put them in a junk GOBIN (the default location), rather than polluting
// the GOBIN that the user will look in.
cmd = exec . Command ( "go" , append ( baseArgs ,
"camlistore.org/pkg/..." ,
"camlistore.org/server/..." ,
"camlistore.org/third_party/..." ,
) ... )
cmd . Env = append ( cleanGoEnv ( ) , "GOPATH=" + buildGoPath )
2013-07-04 02:44:57 +00:00
if * quiet {
cmd . Stdout = & output
cmd . Stderr = & output
} else {
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
}
2013-06-29 20:52:01 +00:00
if * verbose {
2013-07-04 02:44:57 +00:00
log . Printf ( "Running full go install with args %s" , cmd . Args )
2013-06-29 20:52:01 +00:00
}
if err := cmd . Run ( ) ; err != nil {
2013-07-04 02:44:57 +00:00
log . Fatalf ( "Error building full install: %v\n%s" , err , output . String ( ) )
2013-06-29 20:52:01 +00:00
}
2013-06-10 22:55:17 +00:00
}
2013-06-22 03:18:02 +00:00
2013-06-29 20:52:01 +00:00
if ! * quiet {
log . Printf ( "Success. Binaries are in %s" , binDir )
}
2013-06-10 22:55:17 +00:00
}
2013-06-18 22:21:56 +00:00
// cleanGoEnv returns a copy of the current environment with GOPATH and GOBIN removed.
func cleanGoEnv ( ) ( clean [ ] string ) {
for _ , env := range os . Environ ( ) {
if strings . HasPrefix ( env , "GOPATH=" ) || strings . HasPrefix ( env , "GOBIN=" ) {
continue
}
clean = append ( clean , env )
}
2013-07-05 17:41:07 +00:00
if * buildOS != runtime . GOOS {
clean = append ( clean , "GOOS=" + * buildOS )
}
2013-06-18 22:21:56 +00:00
return
}
2013-06-20 13:49:34 +00:00
// buildSrcPath returns the full path concatenation
// of buildSrcDir with fromSrc.
func buildSrcPath ( fromSrc string ) string {
return filepath . Join ( buildSrcDir , filepath . FromSlash ( fromSrc ) )
}
// genEmbeds generates from the static resources the zembed.*.go
// files that will allow for these resources to be included in
// the camlistored binary.
// It also populates wantDestFile with those files so they're
// kept in between runs.
func genEmbeds ( ) error {
cmdName := filepath . Join ( buildGoPath , "bin" , "genfileembed" )
uiEmbeds := buildSrcPath ( "server/camlistored/ui" )
serverEmbeds := buildSrcPath ( "pkg/server" )
for _ , embeds := range [ ] string { uiEmbeds , serverEmbeds } {
args := [ ] string { embeds }
cmd := exec . Command ( cmdName , args ... )
cmd . Env = append ( cleanGoEnv ( ) ,
"GOPATH=" + buildGoPath ,
)
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if * verbose {
log . Printf ( "Running %s %s" , cmdName , embeds )
}
if err := cmd . Run ( ) ; err != nil {
return fmt . Errorf ( "Error running %s %s: %v" , cmdName , embeds , err )
}
// We mark all the zembeds in builddir as wanted, so that we do not
// have to regen them next time, unless they need updating.
f , err := os . Open ( embeds )
if err != nil {
return err
}
defer f . Close ( )
names , err := f . Readdirnames ( - 1 )
if err != nil {
return err
}
for _ , v := range names {
if strings . HasPrefix ( v , "zembed_" ) {
wantDestFile [ filepath . Join ( embeds , v ) ] = true
}
}
}
return nil
}
func buildGenfileembed ( ) error {
2013-06-18 23:04:27 +00:00
args := [ ] string { "install" , "-v" }
if * all {
args = append ( args , "-a" )
}
args = append ( args ,
2013-06-20 13:49:34 +00:00
filepath . FromSlash ( "camlistore.org/pkg/fileembed/genfileembed" ) ,
2013-06-18 23:04:27 +00:00
)
cmd := exec . Command ( "go" , args ... )
// We don't even need to set GOBIN as it defaults to $GOPATH/bin
// and that is where we want genfileembed to go.
cmd . Env = append ( cleanGoEnv ( ) ,
2013-06-20 13:49:34 +00:00
"GOPATH=" + buildGoPath ,
2013-06-18 23:04:27 +00:00
)
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if * verbose {
log . Printf ( "Running go with args %s" , args )
}
if err := cmd . Run ( ) ; err != nil {
return fmt . Errorf ( "Error building genfileembed: %v" , err )
}
if * verbose {
2013-06-20 13:49:34 +00:00
log . Printf ( "genfileembed installed in %s" , filepath . Join ( buildGoPath , "bin" ) )
2013-06-18 23:04:27 +00:00
}
return nil
}
2013-06-10 22:55:17 +00:00
// getVersion returns the version of Camlistore. Either from a VERSION file at the root,
// or from git.
func getVersion ( camRoot string ) string {
slurp , err := ioutil . ReadFile ( filepath . Join ( camRoot , "VERSION" ) )
if err == nil {
return strings . TrimSpace ( string ( slurp ) )
}
out , err := exec . Command ( filepath . Join ( camRoot , "misc" , "gitversion" ) ) . Output ( )
if err != nil {
log . Fatalf ( "Error running ./misc/gitversion to determine Camlistore version: %v" , err )
}
return strings . TrimSpace ( string ( out ) )
}
// verifyCamlistoreRoot crashes if dir isn't the Camlistore root directory.
func verifyCamlistoreRoot ( dir string ) {
testFile := filepath . Join ( dir , "pkg" , "blobref" , "blobref.go" )
if _ , err := os . Stat ( testFile ) ; err != nil {
log . Fatalf ( "make.go must be run from the Camlistore src root directory (where make.go is). Current working directory is %s" , dir )
}
}
func verifyGoVersion ( ) {
_ , err := exec . LookPath ( "go" )
if err != nil {
log . Fatalf ( "Go doesn't appeared to be installed ('go' isn't in your PATH). Install Go 1.1 or newer." )
}
out , err := exec . Command ( "go" , "version" ) . Output ( )
if err != nil {
log . Fatalf ( "Error checking Go version with the 'go' command: %v" , err )
}
fields := strings . Fields ( string ( out ) )
if len ( fields ) < 3 || ! strings . HasPrefix ( string ( out ) , "go version " ) {
log . Fatalf ( "Unexpected output while checking 'go version': %q" , out )
}
version := fields [ 2 ]
switch version {
case "go1" , "go1.0.1" , "go1.0.2" , "go1.0.3" :
log . Fatalf ( "Your version of Go (%s) is too old. Camlistore requires Go 1.1 or later." )
}
}
2013-06-29 20:52:01 +00:00
func mirrorDir ( src , dst string ) ( maxMod time . Time , err error ) {
err = filepath . Walk ( src , func ( path string , fi os . FileInfo , err error ) error {
2013-06-10 22:55:17 +00:00
if err != nil {
return err
}
base := fi . Name ( )
if fi . IsDir ( ) {
2013-06-22 03:18:02 +00:00
if base == "testdata" {
2013-06-10 22:55:17 +00:00
return filepath . SkipDir
}
}
2013-06-20 13:49:34 +00:00
if strings . HasSuffix ( base , "_test.go" ) ||
strings . HasPrefix ( base , ".#" ) ||
! rxMirrored . MatchString ( base ) {
2013-06-10 22:55:17 +00:00
return nil
}
suffix , err := filepath . Rel ( src , path )
if err != nil {
return fmt . Errorf ( "Failed to find Rel(%q, %q): %v" , src , path , err )
}
2013-06-29 20:52:01 +00:00
if t := fi . ModTime ( ) ; t . After ( maxMod ) {
maxMod = t
}
2013-06-10 22:55:17 +00:00
return mirrorFile ( path , filepath . Join ( dst , suffix ) )
} )
2013-06-29 20:52:01 +00:00
return
2013-06-10 22:55:17 +00:00
}
var wantDestFile = make ( map [ string ] bool ) // full dest filename => true
func mirrorFile ( src , dst string ) error {
wantDestFile [ dst ] = true
sfi , err := os . Stat ( src )
if err != nil {
return err
}
if sfi . Mode ( ) & os . ModeType != 0 {
log . Fatalf ( "mirrorFile can't deal with non-regular file %s" , src )
}
dfi , err := os . Stat ( dst )
if err == nil &&
( dfi . Mode ( ) & os . ModeType == 0 ) &&
dfi . Size ( ) == sfi . Size ( ) &&
dfi . ModTime ( ) . Unix ( ) == sfi . ModTime ( ) . Unix ( ) {
// Seems to not be modified.
return nil
}
dstDir := filepath . Dir ( dst )
if err := os . MkdirAll ( dstDir , 0755 ) ; err != nil {
return err
}
df , err := os . Create ( dst )
if err != nil {
return err
}
sf , err := os . Open ( src )
if err != nil {
return err
}
defer sf . Close ( )
n , err := io . Copy ( df , sf )
if err == nil && n != sfi . Size ( ) {
err = fmt . Errorf ( "copied wrong size for %s -> %s: copied %d; want %d" , src , dst , n , sfi . Size ( ) )
}
cerr := df . Close ( )
if err == nil {
err = cerr
}
2013-06-10 23:18:29 +00:00
if err == nil {
err = os . Chtimes ( dst , sfi . ModTime ( ) , sfi . ModTime ( ) )
}
2013-06-10 22:55:17 +00:00
return err
}
func deleteUnwantedOldMirrorFiles ( dir string ) {
filepath . Walk ( dir , func ( path string , fi os . FileInfo , err error ) error {
if err != nil {
log . Fatalf ( "Error stating while cleaning %s: %v" , path , err )
}
if fi . IsDir ( ) {
return nil
}
if ! wantDestFile [ path ] {
2013-06-29 20:52:01 +00:00
if ! * quiet {
log . Printf ( "Deleting old file from temp build dir: %s" , path )
}
2013-06-10 22:55:17 +00:00
return os . Remove ( path )
}
return nil
} )
}
func haveSQLite ( ) bool {
if runtime . GOOS == "windows" {
// TODO: Find some other non-pkg-config way to test, like
// just compiling a small Go program that sees whether
// it's available.
//
// For now:
return false
}
_ , err := exec . LookPath ( "pkg-config" )
if err != nil {
2013-07-02 16:36:33 +00:00
if runtime . GOOS == "darwin" {
// OS X usually doesn't have pkg-config installed. Don't
// call Fatalf() so that the nicer error message in main()
// can be printed.
return false
}
2013-06-10 22:55:17 +00:00
log . Fatalf ( "No pkg-config found. Can't determine whether sqlite3 is available, and where." )
}
2013-06-23 00:20:32 +00:00
cmd := exec . Command ( "pkg-config" , "--libs" , "sqlite3" )
if runtime . GOOS == "darwin" && os . Getenv ( "PKG_CONFIG_PATH" ) == "" {
matches , err := filepath . Glob ( "/usr/local/Cellar/sqlite/*/lib/pkgconfig/sqlite3.pc" )
if err == nil && len ( matches ) > 0 {
2013-06-24 21:03:46 +00:00
cmd . Env = append ( os . Environ ( ) , "PKG_CONFIG_PATH=" + filepath . Dir ( matches [ 0 ] ) )
2013-06-23 00:20:32 +00:00
}
}
out , err := cmd . Output ( )
2013-06-14 20:21:04 +00:00
if err != nil && err . Error ( ) == "exit status 1" {
// This is sloppy (comparing against a string), but
// doing it correctly requires using multiple *.go
// files to portably get the OS-syscall bits, and I
// want to keep make.go a single file.
return false
}
2013-06-10 22:55:17 +00:00
if err != nil {
log . Fatalf ( "Can't determine whether sqlite3 is available, and where. pkg-config error was: %v, %s" , err , out )
}
return strings . TrimSpace ( string ( out ) ) != ""
}
2013-06-18 20:02:03 +00:00
func embedClosure ( closureDir , embedFile string ) error {
if _ , err := os . Stat ( closureDir ) ; err != nil {
return fmt . Errorf ( "Could not stat %v: %v" , closureDir , err )
}
// first, zip it
var zipbuf bytes . Buffer
2013-06-19 01:39:43 +00:00
var zipdest io . Writer = & zipbuf
if os . Getenv ( "CAMLI_WRITE_TMP_ZIP" ) != "" {
f , _ := os . Create ( "/tmp/camli-closure.zip" )
zipdest = io . MultiWriter ( zipdest , f )
defer f . Close ( )
}
2013-06-19 06:14:36 +00:00
var modTime time . Time
2013-06-19 01:39:43 +00:00
w := zip . NewWriter ( zipdest )
2013-06-18 20:02:03 +00:00
err := filepath . Walk ( closureDir , func ( path string , fi os . FileInfo , err error ) error {
if err != nil {
return err
}
suffix , err := filepath . Rel ( closureDir , path )
if err != nil {
return fmt . Errorf ( "Failed to find Rel(%q, %q): %v" , closureDir , path , err )
}
if fi . IsDir ( ) {
return nil
}
2013-06-19 06:14:36 +00:00
if mt := fi . ModTime ( ) ; mt . After ( modTime ) {
modTime = mt
}
2013-06-18 20:02:03 +00:00
b , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
f , err := w . Create ( suffix )
if err != nil {
log . Fatal ( err )
}
_ , err = f . Write ( b )
return err
} )
if err != nil {
return err
}
err = w . Close ( )
if err != nil {
return err
}
// then embed it as a quoted string
var qb bytes . Buffer
fmt . Fprint ( & qb , "package closure\n\n" )
2013-06-19 06:14:36 +00:00
fmt . Fprint ( & qb , "import \"time\"\n\n" )
fmt . Fprint ( & qb , "func init() {\n" )
fmt . Fprintf ( & qb , "\tZipModTime = time.Unix(%d, 0)\n" , modTime . Unix ( ) )
fmt . Fprint ( & qb , "\tZipData = " )
2013-06-18 20:02:03 +00:00
quote ( & qb , zipbuf . Bytes ( ) )
2013-06-19 06:14:36 +00:00
fmt . Fprint ( & qb , "\n}\n" )
2013-06-18 20:02:03 +00:00
// and write to a .go file
// TODO(mpl): do not regenerate the whole zip file if the modtime
// of the z_data.go file is greater than the modtime of all the closure *.js files.
if err := writeFileIfDifferent ( embedFile , qb . Bytes ( ) ) ; err != nil {
return err
}
return nil
}
func writeFileIfDifferent ( filename string , contents [ ] byte ) error {
fi , err := os . Stat ( filename )
if err == nil && fi . Size ( ) == int64 ( len ( contents ) ) && contentsEqual ( filename , contents ) {
return nil
}
return ioutil . WriteFile ( filename , contents , 0644 )
}
func contentsEqual ( filename string , contents [ ] byte ) bool {
got , err := ioutil . ReadFile ( filename )
if err != nil {
return false
}
return bytes . Equal ( got , contents )
}
// quote escapes and quotes the bytes from bs and writes
// them to dest.
func quote ( dest * bytes . Buffer , bs [ ] byte ) {
dest . WriteByte ( '"' )
for _ , b := range bs {
if b == '\n' {
dest . WriteString ( ` \n ` )
2013-06-19 01:39:43 +00:00
continue
2013-06-18 20:02:03 +00:00
}
if b == '\\' {
dest . WriteString ( ` \\ ` )
continue
}
if b == '"' {
dest . WriteString ( ` \" ` )
continue
}
if ( b >= 32 && b <= 126 ) || b == '\t' {
dest . WriteByte ( b )
continue
}
fmt . Fprintf ( dest , "\\x%02x" , b )
}
dest . WriteByte ( '"' )
}