cmd/release: write out windows archive as a zip

Change-Id: I1cdc305f131d1a62a94109c357e20e4f1efb3c09
Reviewed-on: https://go-review.googlesource.com/12560
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/release/release.go b/cmd/release/release.go
index 31f1d31..ce313d5 100644
--- a/cmd/release/release.go
+++ b/cmd/release/release.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// TODO(adg): put windows release in a zip file
-
 // Command release builds a Go release.
 package main
 
 import (
 	"archive/tar"
+	"archive/zip"
 	"bytes"
 	"compress/gzip"
 	"flag"
@@ -45,6 +44,8 @@
 
 var coordClient *buildlet.CoordinatorClient
 
+const releaselet = "releaselet.go"
+
 func main() {
 	flag.Parse()
 
@@ -55,6 +56,10 @@
 		return
 	}
 
+	if _, err := os.Stat(releaselet); err != nil {
+		log.Fatalf("couldn't locate %q: %v", releaselet, err)
+	}
+
 	if *rev == "" {
 		log.Fatal("must specify -rev flag")
 	}
@@ -355,7 +360,6 @@
 
 	b.logf("Pushing and running releaselet.")
 	// TODO(adg): locate releaselet.go in GOPATH
-	const releaselet = "releaselet.go"
 	f, err := os.Open(releaselet)
 	if err != nil {
 		return err
@@ -393,6 +397,9 @@
 		return err
 	}
 
+	if b.OS == "windows" {
+		return b.fetchZip(client)
+	}
 	return b.fetchTarball(client)
 }
 
@@ -406,6 +413,79 @@
 	return b.writeFile(filename, tgz)
 }
 
+func (b *Build) fetchZip(client *buildlet.Client) error {
+	b.logf("Downloading tarball and re-compressing as zip.")
+
+	tgz, err := client.GetTar(".")
+	if err != nil {
+		return err
+	}
+	defer tgz.Close()
+
+	filename := *version + "." + b.String() + ".zip"
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	if err := tgzToZip(f, tgz); err != nil {
+		f.Close()
+		return err
+	}
+	if err := f.Close(); err != nil {
+		return err
+	}
+
+	b.logf("Wrote %q.", filename)
+	return nil
+}
+
+func tgzToZip(w io.Writer, r io.Reader) error {
+	zr, err := gzip.NewReader(r)
+	if err != nil {
+		return err
+	}
+	tr := tar.NewReader(zr)
+
+	zw := zip.NewWriter(w)
+	for {
+		th, err := tr.Next()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return err
+		}
+		fi := th.FileInfo()
+		zh, err := zip.FileInfoHeader(fi)
+		if err != nil {
+			return err
+		}
+		zh.Name = th.Name // for the full path
+		switch strings.ToLower(path.Ext(zh.Name)) {
+		case ".jpg", ".jpeg", ".png", ".gif":
+			// Don't re-compress already compressed files.
+			zh.Method = zip.Store
+		default:
+			zh.Method = zip.Deflate
+		}
+		if fi.IsDir() {
+			zh.Name += "/" // zip prefers a trailing slash
+			zh.Method = zip.Store
+		}
+		w, err := zw.CreateHeader(zh)
+		if err != nil {
+			return err
+		}
+		if fi.IsDir() {
+			continue
+		}
+		if _, err := io.Copy(w, tr); err != nil {
+			return err
+		}
+	}
+	return zw.Close()
+}
+
 // fetchFile fetches the specified directory from the given buildlet, and
 // writes the first file it finds in that directory to dest.
 func (b *Build) fetchFile(client *buildlet.Client, dest, dir string) error {