Package modfile implements a parser and formatter for go.mod and go.work files.
import "golang.org/x/mod/modfile"The modfile package provides functionality to parse, manipulate, and format Go module configuration files (go.mod and go.work). The go.mod syntax is described in the official documentation.
Key features:
Parse and ParseLax functions parse go.mod files and return an abstract syntax treeFile struct has methods like AddNewRequire and DropReplace for programmatic editingFormat function formats a File back to bytes for writingParseWork and WorkFile handle go.work filesThe ParseLax function ignores unknown statements and may be used to parse go.mod files developed with newer versions of Go, ensuring forward compatibility.
var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)Regular expression for validating Go version strings. Matches versions like "1.18", "1.19.2", "1.20rc1".
var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`)Regular expression for toolchain names. Toolchains must be named beginning with go1, like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. This regexp is intentionally loose for forward compatibility.
type File struct {
Module *Module
Go *Go
Toolchain *Toolchain
Godebug []*Godebug
Require []*Require
Exclude []*Exclude
Replace []*Replace
Retract []*Retract
Tool []*Tool
Ignore []*Ignore
Syntax *FileSyntax
}A File is the parsed, interpreted form of a go.mod file. It contains all the directives from the file in structured form.
func (f *File) AddModuleStmt(path string) errorAdds or updates the module statement.
Example:
f.AddModuleStmt("github.com/user/project")func (f *File) AddGoStmt(version string) errorAdds or updates the go statement.
func (f *File) DropGoStmt()Deletes the go statement from the file.
Example:
f.AddGoStmt("1.21")func (f *File) AddToolchainStmt(name string) errorAdds or updates the toolchain statement.
func (f *File) DropToolchainStmt()Deletes the toolchain statement from the file.
Example:
f.AddToolchainStmt("go1.21.0")func (f *File) AddRequire(path, vers string) errorSets the first require line for path to version vers, preserving any existing comments for that line and removing all other lines for path. If no line currently exists for path, AddRequire adds a new line at the end of the last require block.
func (f *File) AddNewRequire(path, vers string, indirect bool)Adds a new require line for path at version vers at the end of the last require block, regardless of any existing require lines for path.
func (f *File) SetRequire(req []*Require)Updates the requirements of f to contain exactly req, preserving the existing block structure and line comment contents (except for 'indirect' markings) for the first requirement on each named module path.
The Syntax field is ignored for the requirements in req. Any requirements not already present in the file are added to the block containing the last require line.
The requirements in req must specify at most one distinct version for each module path. If any existing requirements may be removed, the caller should call File.Cleanup after all edits are complete.
func (f *File) SetRequireSeparateIndirect(req []*Require)Updates the requirements of f to contain the given requirements. Like SetRequire, it adds requirements for new paths, updates versions and "// indirect" comments, and deletes requirements not in req.
As its name suggests, SetRequireSeparateIndirect puts direct and indirect requirements into two separate blocks. It may move requirements between blocks when their indirect markings change, but won't move requirements from other blocks (especially blocks with comments).
If the file initially has one uncommented block of requirements, SetRequireSeparateIndirect will split it into direct-only and indirect-only blocks, aiding in the transition to separate blocks.
func (f *File) DropRequire(path string) errorRemoves require statements for the given path.
Example:
f.AddRequire("golang.org/x/text", "v0.3.7")
f.AddNewRequire("golang.org/x/tools", "v0.1.0", true) // indirect
f.DropRequire("golang.org/x/old")func (f *File) AddExclude(path, vers string) errorAdds an exclude statement to the mod file. Errors if the provided version is not a canonical version string.
func (f *File) DropExclude(path, vers string) errorRemoves an exclude statement.
Example:
f.AddExclude("example.com/bad/module", "v1.0.0")func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) errorAdds or updates a replace statement.
func (f *File) DropReplace(oldPath, oldVers string) errorRemoves a replace statement.
Example:
// Replace with local path
f.AddReplace("example.com/pkg", "", "./local/pkg", "")
// Replace specific version
f.AddReplace("example.com/pkg", "v1.0.0", "example.com/fork", "v1.0.1")func (f *File) AddRetract(vi VersionInterval, rationale string) errorAdds a retract statement to the mod file. Errors if the provided version interval does not consist of canonical version strings.
func (f *File) DropRetract(vi VersionInterval) errorRemoves a retract statement.
Example:
// Retract a single version
vi := modfile.VersionInterval{Low: "v1.0.0", High: "v1.0.0"}
f.AddRetract(vi, "Published accidentally")
// Retract a range
vi = modfile.VersionInterval{Low: "v1.0.0", High: "v1.5.0"}
f.AddRetract(vi, "Security issues")func (f *File) AddGodebug(key, value string) errorSets the first godebug line for key to value, preserving any existing comments for that line and removing all other godebug lines for key. If no line currently exists for key, AddGodebug adds a new line at the end of the last godebug block.
func (f *File) DropGodebug(key string) errorRemoves godebug statements for the given key.
Example:
f.AddGodebug("panicnil", "1")func (f *File) AddTool(path string) errorAdds a new tool directive with the given path. It does nothing if the tool line already exists.
func (f *File) DropTool(path string) errorRemoves a tool directive with the given path. It does nothing if no such tool directive exists.
Example:
f.AddTool("golang.org/x/tools/cmd/stringer")func (f *File) AddIgnore(path string) errorAdds a new ignore directive with the given path. It does nothing if the ignore line already exists.
func (f *File) DropIgnore(path string) errorRemoves an ignore directive with the given path. It does nothing if no such ignore directive exists.
Example:
f.AddIgnore("example.com/deprecated/module")func (f *File) AddComment(text string)Adds a comment to the file.
Example:
f.AddComment("// This is an auto-generated file")func (f *File) Format() ([]byte, error)Formats the file as bytes.
func (f *File) Cleanup()Cleans up the file f after any edit operations. To avoid quadratic behavior, modifications like File.DropRequire clear the entry but do not remove it from the slice. Cleanup cleans out all the cleared entries.
func (f *File) SortBlocks()Sorts the blocks in the file.
Example:
f.SortBlocks()
f.Cleanup()
data, err := f.Format()
if err != nil {
log.Fatal(err)
}
os.WriteFile("go.mod", data, 0644)type WorkFile struct {
Go *Go
Toolchain *Toolchain
Godebug []*Godebug
Use []*Use
Replace []*Replace
Syntax *FileSyntax
}A WorkFile is the parsed, interpreted form of a go.work file.
func (f *WorkFile) AddGoStmt(version string) errorAdds or updates the go statement.
func (f *WorkFile) DropGoStmt()Deletes the go statement from the file.
func (f *WorkFile) AddToolchainStmt(name string) errorAdds or updates the toolchain statement.
func (f *WorkFile) DropToolchainStmt()Deletes the toolchain statement from the file.
func (f *WorkFile) AddUse(diskPath, modulePath string) errorAdds or updates a use directive.
func (f *WorkFile) AddNewUse(diskPath, modulePath string)Adds a new use directive.
func (f *WorkFile) SetUse(dirs []*Use)Sets the use directives.
func (f *WorkFile) DropUse(path string) errorRemoves a use directive.
Example:
f.AddUse("./module1", "example.com/module1")
f.AddUse("./module2", "example.com/module2")func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) errorAdds or updates a replace statement.
func (f *WorkFile) DropReplace(oldPath, oldVers string) errorRemoves a replace statement.
func (f *WorkFile) AddGodebug(key, value string) errorSets the first godebug line for key to value, preserving any existing comments for that line and removing all other godebug lines for key. If no line currently exists for key, AddGodebug adds a new line at the end of the last godebug block.
func (f *WorkFile) DropGodebug(key string) errorRemoves godebug statements for the given key.
func (f *WorkFile) Cleanup()Cleans up the file f after any edit operations. To avoid quadratic behavior, modifications clear the entry but do not remove it from the slice. Cleanup cleans out all the cleared entries.
func (f *WorkFile) SortBlocks()Sorts the blocks in the file.
type Module struct {
Mod module.Version
Deprecated string
Syntax *Line
}A Module is the module statement.
type Go struct {
Version string // "1.23"
Syntax *Line
}A Go is the go statement.
type Toolchain struct {
Name string // "go1.21rc1"
Syntax *Line
}A Toolchain is the toolchain statement.
type Require struct {
Mod module.Version
Indirect bool // has "// indirect" comment
Syntax *Line
}A Require is a single require statement.
type Exclude struct {
Mod module.Version
Syntax *Line
}An Exclude is a single exclude statement.
type Replace struct {
Old module.Version
New module.Version
Syntax *Line
}A Replace is a single replace statement.
type Retract struct {
VersionInterval
Rationale string
Syntax *Line
}A Retract is a single retract statement.
type Godebug struct {
Key string
Value string
Syntax *Line
}A Godebug is a single godebug key=value statement.
type Tool struct {
Path string
Syntax *Line
}A Tool is a single tool statement.
type Ignore struct {
Path string
Syntax *Line
}An Ignore is a single ignore statement.
type Use struct {
Path string // Use path of module.
ModulePath string // Module path in the comment.
Syntax *Line
}A Use is a single directory statement in a go.work file.
type VersionInterval struct {
Low, High string
}A VersionInterval represents a range of versions with upper and lower bounds. Intervals are closed: both bounds are included. When Low is equal to High, the interval may refer to a single version ('v1.2.3') or an interval ('[v1.2.3, v1.2.3]'); both have the same representation.
type FileSyntax struct {
Name string // file path
Comments
Stmt []Expr
}A FileSyntax represents an entire go.mod file.
func (x *FileSyntax) Span() (start, end Position)func (x *FileSyntax) Cleanup()Cleans up the file syntax x after any edit operations. To avoid quadratic behavior, (*Line).markRemoved marks the line as dead by setting line.Token = nil but does not remove it from the slice. After edits have all been indicated, calling Cleanup cleans out the dead lines.
type Expr interface {
// Span returns the start and end position of the expression,
// excluding leading or trailing comments.
Span() (start, end Position)
// Comment returns the comments attached to the expression.
// This method would normally be named 'Comments' but that
// would interfere with embedding a type of the same name.
Comment() *Comments
}An Expr represents an input element.
type Comments struct {
Before []Comment // whole-line comments before this expression
Suffix []Comment // end-of-line comments after this expression
// For top-level expressions only, After lists whole-line
// comments following the expression.
After []Comment
}Comments collects the comments associated with an expression.
func (c *Comments) Comment() *CommentsReturns the receiver. This isn't useful by itself, but a Comments struct is embedded into all the expression implementation types, and this gives each of those a Comment method to satisfy the Expr interface.
type Comment struct {
Start Position
Token string // without trailing newline
Suffix bool // an end of line (not whole line) comment
}A Comment represents a single // comment.
type CommentBlock struct {
Comments
Start Position
}A CommentBlock represents a top-level block of comments separate from any rule.
func (x *CommentBlock) Span() (start, end Position)type Line struct {
Comments
Start Position
Token []string
InBlock bool
End Position
}A Line is a single line of tokens.
func (x *Line) Span() (start, end Position)type LineBlock struct {
Comments
Start Position
LParen LParen
Token []string
Line []*Line
RParen RParen
}A LineBlock is a factored block of lines, like:
require (
"x"
"y"
)func (x *LineBlock) Span() (start, end Position)type LParen struct {
Comments
Pos Position
}An LParen represents the beginning of a parenthesized line block. It is a place to store suffix comments.
func (x *LParen) Span() (start, end Position)type RParen struct {
Comments
Pos Position
}An RParen represents the end of a parenthesized line block. It is a place to store whole-line (before) comments.
func (x *RParen) Span() (start, end Position)type Position struct {
Line int // line in input (starting at 1)
LineRune int // rune in line (starting at 1)
Byte int // byte in input (starting at 0)
}A Position describes an arbitrary source position in a file, including the file, line, column, and byte offset.
type Error struct {
Filename string
Pos Position
Verb string
ModPath string
Err error
}func (e *Error) Error() string
func (e *Error) Unwrap() errortype ErrorList []Errorfunc (e ErrorList) Error() stringtype VersionFixer func(path, version string) (string, error)A function type for fixing/canonicalizing version strings. Used when parsing to normalize version strings to their canonical form.
func Parse(file string, data []byte, fix VersionFixer) (*File, error)Parses and returns a go.mod file.
Parameters:
file: The name of the file, used in positions and errorsdata: The content of the filefix: An optional function that canonicalizes module versions. If fix is nil, all module versions must be canonical (module.CanonicalVersion must return the same string)Example:
data, err := os.ReadFile("go.mod")
if err != nil {
log.Fatal(err)
}
f, err := modfile.Parse("go.mod", data, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Module: %s\n", f.Module.Mod.Path)func ParseLax(file string, data []byte, fix VersionFixer) (*File, error)Like Parse but ignores unknown statements. It is used when parsing go.mod files other than the main module, under the theory that most statement types added in the future will only apply in the main module (like exclude and replace), and so older Go commands can simply ignore those statements when found in go.mod files in dependencies.
Example:
// Parse a go.mod from a dependency that might use newer syntax
f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
log.Fatal(err)
}func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error)Parses and returns a go.work file.
Parameters:
file: The name of the file, used in positions and errorsdata: The content of the filefix: An optional function that canonicalizes module versionsExample:
data, err := os.ReadFile("go.work")
if err != nil {
log.Fatal(err)
}
wf, err := modfile.ParseWork("go.work", data, nil)
if err != nil {
log.Fatal(err)
}
for _, use := range wf.Use {
fmt.Printf("Use: %s\n", use.Path)
}func Format(f *FileSyntax) []byteReturns a go.mod file as a byte slice, formatted in standard style.
Example:
formatted := modfile.Format(f.Syntax)
os.WriteFile("go.mod", formatted, 0644)func ModulePath(mod []byte) stringReturns the module path from the gomod file text. If it cannot find a module path, it returns an empty string. It is tolerant of unrelated problems in the go.mod file.
Example:
data, _ := os.ReadFile("go.mod")
path := modfile.ModulePath(data)
fmt.Printf("Module path: %s\n", path)func AutoQuote(s string) stringReturns s or, if quoting is required for s to appear in a go.mod, the quotation of s.
Example:
quoted := modfile.AutoQuote("path/with spaces")
fmt.Println(quoted) // Output: "path/with spaces"func MustQuote(s string) boolReports whether s must be quoted in order to appear as a single token in a go.mod line.
Example:
if modfile.MustQuote("path with spaces") {
fmt.Println("This path needs quoting")
}func IsDirectoryPath(ns string) boolReports whether the given path should be interpreted as a directory path. Just like on the go command line, relative paths starting with a '.' or '..' path component and rooted paths are directory paths; the rest are module paths.
Example:
fmt.Println(modfile.IsDirectoryPath("./local")) // true
fmt.Println(modfile.IsDirectoryPath("../other")) // true
fmt.Println(modfile.IsDirectoryPath("/abs/path")) // true
fmt.Println(modfile.IsDirectoryPath("example.com")) // falsepackage main
import (
"fmt"
"log"
"os"
"golang.org/x/mod/modfile"
)
func main() {
data, err := os.ReadFile("go.mod")
if err != nil {
log.Fatal(err)
}
f, err := modfile.Parse("go.mod", data, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Module: %s\n", f.Module.Mod.Path)
if f.Go != nil {
fmt.Printf("Go version: %s\n", f.Go.Version)
}
fmt.Println("\nRequirements:")
for _, req := range f.Require {
indirect := ""
if req.Indirect {
indirect = " // indirect"
}
fmt.Printf(" %s %s%s\n", req.Mod.Path, req.Mod.Version, indirect)
}
if len(f.Replace) > 0 {
fmt.Println("\nReplacements:")
for _, rep := range f.Replace {
fmt.Printf(" %s => %s\n", rep.Old.Path, rep.New.Path)
}
}
}package main
import (
"fmt"
"log"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)
func main() {
f := &modfile.File{}
// Set module path
if err := f.AddModuleStmt("example.com/myproject"); err != nil {
log.Fatal(err)
}
// Set Go version
if err := f.AddGoStmt("1.21"); err != nil {
log.Fatal(err)
}
// Add requirements
f.AddNewRequire("golang.org/x/text", "v0.14.0", false)
f.AddNewRequire("golang.org/x/tools", "v0.16.0", true) // indirect
// Add a replacement
if err := f.AddReplace("old.com/pkg", "", "./local/pkg", ""); err != nil {
log.Fatal(err)
}
// Format and write
data, err := f.Format()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}package main
import (
"log"
"os"
"golang.org/x/mod/modfile"
)
func main() {
// Read existing file
data, err := os.ReadFile("go.mod")
if err != nil {
log.Fatal(err)
}
f, err := modfile.Parse("go.mod", data, nil)
if err != nil {
log.Fatal(err)
}
// Update Go version
if err := f.AddGoStmt("1.21"); err != nil {
log.Fatal(err)
}
// Add a new requirement
if err := f.AddRequire("github.com/new/package", "v1.0.0"); err != nil {
log.Fatal(err)
}
// Remove old requirement
if err := f.DropRequire("github.com/old/package"); err != nil {
log.Fatal(err)
}
// Clean up and format
f.Cleanup()
f.SortBlocks()
data, err = f.Format()
if err != nil {
log.Fatal(err)
}
// Write back
if err := os.WriteFile("go.mod", data, 0644); err != nil {
log.Fatal(err)
}
}package main
import (
"fmt"
"log"
"os"
"golang.org/x/mod/modfile"
)
func main() {
data, err := os.ReadFile("go.work")
if err != nil {
log.Fatal(err)
}
wf, err := modfile.ParseWork("go.work", data, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Workspace modules:")
for _, use := range wf.Use {
fmt.Printf(" %s", use.Path)
if use.ModulePath != "" {
fmt.Printf(" (%s)", use.ModulePath)
}
fmt.Println()
}
// Add a new module to workspace
wf.AddNewUse("./newmodule", "example.com/newmodule")
// Format and save
wf.Cleanup()
wf.SortBlocks()
formatted := modfile.Format(wf.Syntax)
if err := os.WriteFile("go.work", formatted, 0644); err != nil {
log.Fatal(err)
}
}package main
import (
"fmt"
"os"
"golang.org/x/mod/modfile"
)
func main() {
data, err := os.ReadFile("go.mod")
if err != nil {
panic(err)
}
// Quick extraction without full parsing
path := modfile.ModulePath(data)
fmt.Printf("Module path: %s\n", path)
}package main
import (
"fmt"
"log"
"os"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)
func main() {
data, err := os.ReadFile("go.mod")
if err != nil {
log.Fatal(err)
}
// Define a version fixer function
fix := func(path, version string) (string, error) {
canonical := module.CanonicalVersion(version)
if canonical == "" {
return version, fmt.Errorf("invalid version %q", version)
}
return canonical, nil
}
f, err := modfile.Parse("go.mod", data, fix)
if err != nil {
log.Fatal(err)
}
// All versions are now canonicalized
for _, req := range f.Require {
fmt.Printf("%s %s\n", req.Mod.Path, req.Mod.Version)
}
}