func TestZipWriter(t *testing.T) { // Have to write an actual file for zip.OpenReader to use later. f, err := ioutil.TempFile("", "zip_writer_test") if err != nil { t.Fatalf("Failed to create temp file: %s", err) } filename := f.Name() defer os.Remove(filename) w := zip.NewWriter(f) if err := AddZipFile(w, "src/build/java/test_data/test.zip", nil, nil, "", true, nil); err != nil { t.Fatalf("Failed to add zip file: %s", err) } if err := w.Close(); err != nil { t.Fatalf("Failed to close zip file: %s", err) } w.Close() f.Close() r, err := zip.OpenReader(filename) if err != nil { t.Fatalf("Failed to reopen zip file: %s", err) } defer r.Close() files := []struct{ Name, Prefix string }{ {"build_step.go", "// Implementation of Step interface."}, {"incrementality.go", "// Utilities to help with incremental builds."}, } for i, f := range r.File { if f.Name != files[i].Name { t.Errorf("File %d has wrong name: expected %s, was %s", i, files[i].Name, f.Name) } fr, err := f.Open() if err != nil { t.Errorf("Failed to reopen file %d [%s]: %s", i, f.Name, err) } else { buf := new(bytes.Buffer) if _, err = io.Copy(buf, fr); err != nil { t.Errorf("Failed to read full contents of file %d [%s]: %s", i, f.Name, err) } else if !strings.HasPrefix(buf.String(), files[i].Prefix) { t.Errorf("File %d [%s] didn't start with expected prefix: was %s", buf.String()[:20]) } fr.Close() } } }
// AddZipFile copies the contents of a zip file into an existing zip writer. func AddZipFile(w *zip.Writer, filepath string, include, exclude []string, stripPrefix string, strict bool, renameDirs map[string]string) error { r, err := zip.OpenReader(filepath) if err != nil { return err } defer r.Close() // Reopen file to get a directly readable version without decompression. r2, err := os.Open(filepath) if err != nil { return err } defer r2.Close() outer: for _, f := range r.File { log.Debug("Found file %s (from %s)", f.Name, filepath) // This directory is very awkward. We need to merge the contents by concatenating them, // we can't replace them or leave them out. if strings.HasPrefix(f.Name, "META-INF/services/") || strings.HasPrefix(f.Name, "META-INF/spring") || f.Name == "META-INF/please_sourcemap" { if err := concatenateFile(w, f); err != nil { return err } continue } if !shouldInclude(f.Name, include, exclude) { continue outer } hasTrailingSlash := strings.HasSuffix(f.Name, "/") isDir := hasTrailingSlash || f.FileInfo().IsDir() if isDir && !hasTrailingSlash { f.Name = f.Name + "/" } if existing, present := getExistingFile(w, f.Name); present { // Allow duplicates of directories. Seemingly the best way to identify them is that // they end in a trailing slash. if isDir { continue } // Allow skipping existing files that are exactly the same as the added ones. // It's unnecessarily awkward to insist on not ever doubling up on a dependency. // TODO(pebers): Bit of a hack ignoring it when CRC is 0, would be better to add // the correct CRC when added through WriteFile. if existing.CRC32 == f.CRC32 || existing.CRC32 == 0 { log.Info("Skipping %s / %s: already added (from %s)", filepath, f.Name, existing.ZipFile) continue } if strict { log.Error("Duplicate file %s (from %s, already added from %s); crc %d / %d", f.Name, filepath, existing.ZipFile, f.CRC32, existing.CRC32) return fmt.Errorf("File %s already added to destination zip file (from %s)", f.Name, existing.ZipFile) } continue } for before, after := range renameDirs { if strings.HasPrefix(f.Name, before) { f.Name = path.Join(after, strings.TrimPrefix(f.Name, before)) if isDir { f.Name = f.Name + "/" } break } } f.Name = strings.TrimPrefix(f.Name, stripPrefix) // Java tools don't seem to like writing a data descriptor for stored items. // Unsure if this is a limitation of the format or a problem of those tools. f.Flags = 0 addExistingFile(w, f.Name, filepath, f.CompressedSize64, f.UncompressedSize64, f.CRC32) start, err := f.DataOffset() if err != nil { return err } if _, err := r2.Seek(start, 0); err != nil { return err } // Make these deterministic. f.FileHeader.ModifiedDate = 0 f.FileHeader.ModifiedTime = 0 if err := addFile(w, &f.FileHeader, r2, f.CRC32); err != nil { return err } } return nil }