or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

dirhash.mdgosumcheck.mdindex.mdmodfile.mdmodule.mdnote.mdsemver.mdstorage.mdsumdb.mdtlog.mdzip.md
tile.json

zip.mddocs/

zip - Module Zip File Creation and Extraction

Package zip provides functions for creating and extracting module zip files.

Import

import "golang.org/x/mod/zip"

Overview

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 File Restrictions

Module zip files must satisfy these requirements:

  1. 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)
    • Path after prefix must be valid (see module.CheckFilePath)
  2. Case Sensitivity: No two file paths may be equal under Unicode case-folding

  3. go.mod Placement: A go.mod file may appear only in the top-level directory (if present at all)

  4. Size Limits:

    • Total zip size: ≤ 500 MiB
    • Total uncompressed size: ≤ 500 MiB
    • go.mod file: ≤ 16 MiB
    • LICENSE file: ≤ 16 MiB
  5. File Metadata: Empty directories, file permissions, and timestamps are ignored

  6. File Types: Symbolic links and other irregular files are not allowed

Constants

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
)

Types

File

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.

CheckedFiles

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.

Methods

func (cf CheckedFiles) Err() error

Returns 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.

FileError

type FileError struct {
    Path string
    Err  error
}

Methods

func (e FileError) Error() string
func (e FileError) Unwrap() error

FileErrorList

type FileErrorList []FileError

Methods

func (el FileErrorList) Error() string

UnrecognizedVCSError

type UnrecognizedVCSError struct {
    RepoRoot string
}

UnrecognizedVCSError indicates that no recognized version control system was found in the given directory.

Methods

func (e *UnrecognizedVCSError) Error() string

Functions

Creating Zip Files

func Create(w io.Writer, m module.Version, files []File) error

Builds 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) error

Creates 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) error

Creates 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)
}

Extracting Zip Files

func Unzip(dir string, m module.Version, zipFile string) error

Extracts 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)
}

Checking and Validating

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.

Usage Examples

Creating a Zip from Directory

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")
}

Extracting and Verifying a 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")
}

Creating Zip from VCS

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")
}

Inspecting Zip Contents

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)
    }
}

Custom File Implementation

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)
}

Batch Processing

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)
    }
}

Common Patterns

Safe Extraction

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
}

Validation Before Upload

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
}

File Exclusions

Files automatically excluded from module zips:

  • VCS directories: .git, .hg, .svn, .bzr
  • Vendor directories: vendor/ (in most cases)
  • Nested modules: Subdirectories with their own go.mod
  • Irregular files: Symbolic links, devices, sockets
  • Hidden files: May be excluded depending on context

See Also

  • module - Module path and version validation
  • dirhash - Computing hashes of module content
  • Go Modules Reference