Package zip provides functions for creating and extracting module zip files.
import "golang.org/x/mod/zip"The zip package handles the creation and extraction of Go module zip files according to the official Go module zip format specification. Module zip files have strict requirements to ensure consistent extraction across platforms and file systems.
Module zip files must satisfy these requirements:
Path Prefix: All file paths must start with <module>@<version>/ where:
<module> is a valid module path (see module.CheckPath)<version> is a valid, canonical version (see module.CanonicalVersion)module.CheckFilePath)Case Sensitivity: No two file paths may be equal under Unicode case-folding
go.mod Placement: A go.mod file may appear only in the top-level directory (if present at all)
Size Limits:
File Metadata: Empty directories, file permissions, and timestamps are ignored
File Types: Symbolic links and other irregular files are not allowed
const (
// MaxZipFile is the maximum size in bytes of a module zip file. The
// go command will report an error if either the zip file or its extracted
// content is larger than this.
MaxZipFile = 500 << 20
// MaxGoMod is the maximum size in bytes of a go.mod file within a
// module zip file.
MaxGoMod = 16 << 20
// MaxLICENSE is the maximum size in bytes of a LICENSE file within a
// module zip file.
MaxLICENSE = 16 << 20
)type File interface {
// Path returns a clean slash-separated relative path from the module root
// directory to the file.
Path() string
// Lstat returns information about the file. If the file is a symbolic link,
// Lstat returns information about the link itself, not the file it points to.
Lstat() (os.FileInfo, error)
// Open provides access to the data within a regular file. Open may return
// an error if called on a directory or symbolic link.
Open() (io.ReadCloser, error)
}File provides an abstraction for a file in a directory, zip, or anything else that looks like a file.
type CheckedFiles struct {
// Valid is a list of file paths that should be included in a zip file.
Valid []string
// Omitted is a list of files that are ignored when creating a module zip
// file, along with the reason each file is ignored.
Omitted []FileError
// Invalid is a list of files that should not be included in a module zip
// file, along with the reason each file is invalid.
Invalid []FileError
// SizeError is non-nil if the total uncompressed size of the valid files
// exceeds the module zip size limit or if the zip file itself exceeds the
// limit.
SizeError error
}CheckedFiles reports whether a set of files satisfy the name and size constraints required by module zip files.
func (cf CheckedFiles) Err() errorReturns an error if CheckedFiles does not describe a valid module zip file. Returns CheckedFiles.SizeError if set, or a FileErrorList if there are invalid files.
type FileError struct {
Path string
Err error
}func (e FileError) Error() string
func (e FileError) Unwrap() errortype FileErrorList []FileErrorfunc (el FileErrorList) Error() stringtype UnrecognizedVCSError struct {
RepoRoot string
}UnrecognizedVCSError indicates that no recognized version control system was found in the given directory.
func (e *UnrecognizedVCSError) Error() stringfunc Create(w io.Writer, m module.Version, files []File) errorBuilds a zip archive for module m from an abstract list of files and writes it to w. Verifies restrictions and does not include files that don't belong in the module zip.
Example:
m := module.Version{Path: "example.com/mymodule", Version: "v1.0.0"}
files := []zip.File{/* ... */}
f, _ := os.Create("module.zip")
defer f.Close()
if err := zip.Create(f, m, files); err != nil {
log.Fatal(err)
}func CreateFromDir(w io.Writer, m module.Version, dir string) errorCreates a module zip file for module m from the contents of directory dir. Does not include files from subdirectory modules, vendor directories, VCS directories (.git, .hg, etc.), or irregular files.
Example:
m := module.Version{Path: "example.com/mymodule", Version: "v1.0.0"}
f, _ := os.Create("module.zip")
defer f.Close()
if err := zip.CreateFromDir(f, m, "./mymodule"); err != nil {
log.Fatal(err)
}func CreateFromVCS(w io.Writer, m module.Version, repoRoot, revision, subdir string) errorCreates a module zip file for module m from the contents of a VCS repository.
Parameters:
repoRoot: Absolute path to repository base (must point to worktree for Git)revision: Revision to create zip from (e.g., "HEAD", commit SHA)subdir: Relative path from repository base (empty string for repo root)If returns UnrecognizedVCSError, consider falling back to CreateFromDir.
Example:
m := module.Version{Path: "example.com/mymodule", Version: "v1.0.0"}
f, _ := os.Create("module.zip")
defer f.Close()
err := zip.CreateFromVCS(f, m, "/path/to/repo", "v1.0.0", "")
if _, ok := err.(*zip.UnrecognizedVCSError); ok {
// Fall back to CreateFromDir
err = zip.CreateFromDir(f, m, "/path/to/repo")
}
if err != nil {
log.Fatal(err)
}func Unzip(dir string, m module.Version, zipFile string) errorExtracts the contents of a module zip file to a directory. Checks all restrictions and returns an error if the zip is invalid. Creates dir and parent directories if they don't exist. If dir exists, it must be empty.
Example:
m := module.Version{Path: "example.com/mymodule", Version: "v1.0.0"}
if err := zip.Unzip("./extracted", m, "module.zip"); err != nil {
log.Fatal(err)
}func CheckZip(m module.Version, zipFile string) (CheckedFiles, error)Reports whether the files contained in a zip file satisfy the name and size constraints. Returns an error if the zip is invalid, but still populates CheckedFiles.
Example:
m := module.Version{Path: "example.com/mymodule", Version: "v1.0.0"}
cf, err := zip.CheckZip(m, "module.zip")
if err != nil {
log.Printf("Invalid zip: %v", err)
}
fmt.Printf("Valid files: %d\n", len(cf.Valid))
fmt.Printf("Invalid files: %d\n", len(cf.Invalid))
fmt.Printf("Omitted files: %d\n", len(cf.Omitted))func CheckDir(dir string) (CheckedFiles, error)Reports whether the files in dir satisfy the name and size constraints. Does not open files, so CreateFromDir may still fail due to I/O errors.
Example:
cf, err := zip.CheckDir("./mymodule")
if err != nil {
log.Fatal(err)
}
if cf.Err() != nil {
log.Printf("Directory has issues: %v", cf.Err())
}func CheckFiles(files []File) (CheckedFiles, error)Reports whether a list of files satisfy the constraints. Every file in the list appears in exactly one of Valid, Invalid, or Omitted.
package main
import (
"log"
"os"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
func main() {
m := module.Version{
Path: "example.com/mymodule",
Version: "v1.0.0",
}
// Check directory first
cf, err := zip.CheckDir("./mymodule")
if err != nil {
log.Fatal(err)
}
if cf.Err() != nil {
log.Fatalf("Directory validation failed: %v", cf.Err())
}
// Create zip file
f, err := os.Create("mymodule.zip")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := zip.CreateFromDir(f, m, "./mymodule"); err != nil {
log.Fatal(err)
}
log.Println("Successfully created module zip")
}package main
import (
"log"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
func main() {
m := module.Version{
Path: "example.com/mymodule",
Version: "v1.0.0",
}
// Check the zip first
cf, err := zip.CheckZip(m, "downloaded.zip")
if err != nil {
log.Fatalf("Zip check failed: %v", err)
}
if len(cf.Invalid) > 0 {
log.Printf("Warning: %d invalid files found", len(cf.Invalid))
for _, fe := range cf.Invalid {
log.Printf(" %s: %v", fe.Path, fe.Err)
}
}
// Extract
if err := zip.Unzip("./extracted", m, "downloaded.zip"); err != nil {
log.Fatal(err)
}
log.Println("Successfully extracted module")
}package main
import (
"log"
"os"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
func main() {
m := module.Version{
Path: "example.com/mymodule",
Version: "v1.0.0",
}
f, err := os.Create("module.zip")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Try VCS first
err = zip.CreateFromVCS(
f,
m,
"/absolute/path/to/repo",
"v1.0.0",
"", // subdir: empty = repo root
)
if _, ok := err.(*zip.UnrecognizedVCSError); ok {
log.Println("VCS not recognized, falling back to directory")
f.Seek(0, 0)
f.Truncate(0)
err = zip.CreateFromDir(f, m, "/absolute/path/to/repo")
}
if err != nil {
log.Fatal(err)
}
log.Println("Successfully created zip from VCS")
}package main
import (
"fmt"
"log"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
func main() {
m := module.Version{
Path: "example.com/mymodule",
Version: "v1.0.0",
}
cf, err := zip.CheckZip(m, "module.zip")
if err != nil {
log.Printf("Error checking zip: %v", err)
}
fmt.Println("Valid files:")
for _, path := range cf.Valid {
fmt.Printf(" %s\n", path)
}
if len(cf.Omitted) > 0 {
fmt.Println("\nOmitted files:")
for _, fe := range cf.Omitted {
fmt.Printf(" %s: %v\n", fe.Path, fe.Err)
}
}
if len(cf.Invalid) > 0 {
fmt.Println("\nInvalid files:")
for _, fe := range cf.Invalid {
fmt.Printf(" %s: %v\n", fe.Path, fe.Err)
}
}
if cf.SizeError != nil {
fmt.Printf("\nSize error: %v\n", cf.SizeError)
}
}package main
import (
"io"
"os"
"path/filepath"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
type customFile struct {
path string
info os.FileInfo
}
func (f *customFile) Path() string {
return f.path
}
func (f *customFile) Lstat() (os.FileInfo, error) {
return f.info, nil
}
func (f *customFile) Open() (io.ReadCloser, error) {
return os.Open(filepath.Join("/base/dir", f.path))
}
func main() {
files := []zip.File{
&customFile{path: "go.mod", info: /* ... */},
&customFile{path: "main.go", info: /* ... */},
}
m := module.Version{
Path: "example.com/mymodule",
Version: "v1.0.0",
}
f, _ := os.Create("custom.zip")
defer f.Close()
zip.Create(f, m, files)
}package main
import (
"fmt"
"log"
"os"
"path/filepath"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
)
func processModules(modules []struct {
path string
version string
dir string
}) {
for _, mod := range modules {
m := module.Version{
Path: mod.path,
Version: mod.version,
}
zipName := fmt.Sprintf("%s@%s.zip",
filepath.Base(mod.path), mod.version)
f, err := os.Create(zipName)
if err != nil {
log.Printf("Failed to create %s: %v", zipName, err)
continue
}
err = zip.CreateFromDir(f, m, mod.dir)
f.Close()
if err != nil {
log.Printf("Failed to zip %s: %v", mod.path, err)
os.Remove(zipName)
continue
}
log.Printf("Created %s", zipName)
}
}func safeUnzip(zipFile string, m module.Version) error {
// Check before extracting
cf, err := zip.CheckZip(m, zipFile)
if err != nil {
return fmt.Errorf("pre-check failed: %w", err)
}
if cf.Err() != nil {
return fmt.Errorf("invalid zip: %w", cf.Err())
}
// Create temp directory
tmpDir, err := os.MkdirTemp("", "unzip-*")
if err != nil {
return err
}
// Extract to temp first
if err := zip.Unzip(tmpDir, m, zipFile); err != nil {
os.RemoveAll(tmpDir)
return err
}
// Move to final location
finalDir := filepath.Join("modules", m.Path+"@"+m.Version)
if err := os.Rename(tmpDir, finalDir); err != nil {
os.RemoveAll(tmpDir)
return err
}
return nil
}func validateBeforeUpload(dir string) error {
cf, err := zip.CheckDir(dir)
if err != nil {
return err
}
if len(cf.Invalid) > 0 {
return fmt.Errorf("directory contains %d invalid files", len(cf.Invalid))
}
if cf.SizeError != nil {
return cf.SizeError
}
return nil
}Files automatically excluded from module zips:
.git, .hg, .svn, .bzrvendor/ (in most cases)