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  }