5

modfile: support retract directive and version intervals · golang/mod@c0d644d ·...

 3 years ago
source link: https://github.com/golang/mod/commit/c0d644d00ab849f4506f17a98a5740bf0feff020
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
support retract directive and version intervals · golang/mod@c0d644d · GitHub

Browse files

modfile: support retract directive and version intervals

This CL adds support for parsing and programmatically adding and
removing a new directive, "retract", as described in golang/go#24031.

The "retract" directive comes in two forms:

    retract v1.0.0           // single version
    retract [v1.1.0, v1.2.0] // closed interval

Updates golang/go#24031

Change-Id: I1236c7d89e7674abf694e49e9b4869b14a59fac0
Reviewed-on: https://go-review.googlesource.com/c/mod/+/228039
Run-TryBot: Jay Conrod <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Michael Matloob <[email protected]>

jayconrod

committed on Aug 25

1 parent 89ce4c7 commit c0d644d00ab849f4506f17a98a5740bf0feff020
Showing with 734 additions and 88 deletions.

@@ -30,6 +30,7 @@ import (

"golang.org/x/mod/internal/lazyregexp"

"golang.org/x/mod/module"

"golang.org/x/mod/semver"

)

// A File is the parsed, interpreted form of a go.mod file.

@@ -39,6 +40,7 @@ type File struct {

Require []*Require

Exclude []*Exclude

Replace []*Replace

Retract []*Retract

Syntax *FileSyntax

}

@@ -75,6 +77,21 @@ type Replace struct {

Syntax *Line

}

// A Retract is a single retract statement.

type Retract struct {

VersionInterval

Rationale string

Syntax *Line

}

// 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 VersionInterval struct {

Low, High string

}

func (f *File) AddModuleStmt(path string) error {

if f.Syntax == nil {

f.Syntax = new(FileSyntax)

@@ -138,7 +155,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File

for _, x := range fs.Stmt {

switch x := x.(type) {

case *Line:

f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)

f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict)

case *LineBlock:

if len(x.Token) > 1 {

@@ -161,9 +178,9 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File

})

}

continue

case "module", "require", "exclude", "replace":

case "module", "require", "exclude", "replace", "retract":

for _, l := range x.Line {

f.add(&errs, l, x.Token[0], l.Token, fix, strict)

f.add(&errs, x, l, x.Token[0], l.Token, fix, strict)

}

}

}

@@ -177,7 +194,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File

var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)

func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer, strict bool) {

func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {

// If strict is false, this module is a dependency.

// We ignore all unknown directives as well as main-module-only

// directives like replace and exclude. It will work better for

@@ -186,7 +203,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix

// and simply ignore those statements.

if !strict {

switch verb {

case "module", "require", "go":

case "go", "module", "retract", "require":

// want these even for dependency go.mods

default:

return

@@ -232,6 +249,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix

f.Go = &Go{Syntax: line}

f.Go.Version = args[0]

case "module":

if f.Module != nil {

errorf("repeated module statement")

@@ -248,6 +266,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix

return

}

f.Module.Mod = module.Version{Path: s}

case "require", "exclude":

if len(args) != 2 {

errorf("usage: %s module/path v1.2.3", verb)

@@ -284,6 +303,7 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix

Syntax: line,

})

}

case "replace":

arrow := 2

if len(args) >= 2 && args[1] == "=>" {

@@ -347,6 +367,33 @@ func (f *File) add(errs *ErrorList, line *Line, verb string, args []string, fix

New: module.Version{Path: ns, Version: nv},

Syntax: line,

})

case "retract":

rationale := parseRetractRationale(block, line)

vi, err := parseVersionInterval(verb, &args, fix)

if err != nil {

if strict {

wrapError(err)

return

} else {

// Only report errors parsing intervals in the main module. We may

// support additional syntax in the future, such as open and half-open

// intervals. Those can't be supported now, because they break the

// go.mod parser, even in lax mode.

return

}

}

if len(args) > 0 && strict {

// In the future, there may be additional information after the version.

errorf("unexpected token after version: %q", args[0])

return

}

retract := &Retract{

VersionInterval: vi,

Rationale: rationale,

Syntax: line,

}

f.Retract = append(f.Retract, retract)

}

}

@@ -444,6 +491,53 @@ func AutoQuote(s string) string {

return s

}

func parseVersionInterval(verb string, args *[]string, fix VersionFixer) (VersionInterval, error) {

toks := *args

if len(toks) == 0 || toks[0] == "(" {

return VersionInterval{}, fmt.Errorf("expected '[' or version")

}

if toks[0] != "[" {

v, err := parseVersion(verb, "", &toks[0], fix)

if err != nil {

return VersionInterval{}, err

}

*args = toks[1:]

return VersionInterval{Low: v, High: v}, nil

}

toks = toks[1:]

if len(toks) == 0 {

return VersionInterval{}, fmt.Errorf("expected version after '['")

}

low, err := parseVersion(verb, "", &toks[0], fix)

if err != nil {

return VersionInterval{}, err

}

toks = toks[1:]

if len(toks) == 0 || toks[0] != "," {

return VersionInterval{}, fmt.Errorf("expected ',' after version")

}

toks = toks[1:]

if len(toks) == 0 {

return VersionInterval{}, fmt.Errorf("expected version after ','")

}

high, err := parseVersion(verb, "", &toks[0], fix)

if err != nil {

return VersionInterval{}, err

}

toks = toks[1:]

if len(toks) == 0 || toks[0] != "]" {

return VersionInterval{}, fmt.Errorf("expected ']' after version")

}

toks = toks[1:]

*args = toks

return VersionInterval{Low: low, High: high}, nil

}

func parseString(s *string) (string, error) {

t := *s

if strings.HasPrefix(t, `"`) {

@@ -461,6 +555,27 @@ func parseString(s *string) (string, error) {

return t, nil

}

// parseRetractRationale extracts the rationale for a retract directive from the

// surrounding comments. If the line does not have comments and is part of a

// block that does have comments, the block's comments are used.

func parseRetractRationale(block *LineBlock, line *Line) string {

comments := line.Comment()

if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {

comments = block.Comment()

}

groups := [][]Comment{comments.Before, comments.Suffix}

var lines []string

for _, g := range groups {

for _, c := range g {

if !strings.HasPrefix(c.Token, "//") {

continue // blank line

}

lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//")))

}

}

return strings.Join(lines, "\n")

}

type ErrorList []Error

func (e ErrorList) Error() string {

@@ -494,6 +609,8 @@ func (e *Error) Error() string {

var directive string

if e.ModPath != "" {

directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath)

} else if e.Verb != "" {

directive = fmt.Sprintf("%s: ", e.Verb)

}

return pos + directive + e.Err.Error()

@@ -585,6 +702,15 @@ func (f *File) Cleanup() {

}

f.Replace = f.Replace[:w]

w = 0

for _, r := range f.Retract {

if r.Low != "" || r.High != "" {

f.Retract[w] = r

w++

}

}

f.Retract = f.Retract[:w]

f.Syntax.Cleanup()

}

@@ -778,6 +904,34 @@ func (f *File) DropReplace(oldPath, oldVers string) error {

return nil

}

func (f *File) AddRetract(vi VersionInterval, rationale string) error {

r := &Retract{

VersionInterval: vi,

}

if vi.Low == vi.High {

r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low))

} else {

r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")

}

if rationale != "" {

for _, line := range strings.Split(rationale, "\n") {

com := Comment{Token: "// " + line}

r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)

}

}

return nil

}

func (f *File) DropRetract(vi VersionInterval) error {

for _, r := range f.Retract {

if r.VersionInterval == vi {

f.Syntax.removeLine(r.Syntax)

*r = Retract{}

}

}

return nil

}

func (f *File) SortBlocks() {

f.removeDups() // otherwise sorting is unsafe

@@ -786,28 +940,38 @@ func (f *File) SortBlocks() {

if !ok {

continue

}

sort.Slice(block.Line, func(i, j int) bool {

li := block.Line[i]

lj := block.Line[j]

for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {

if li.Token[k] != lj.Token[k] {

return li.Token[k] < lj.Token[k]

}

}

return len(li.Token) < len(lj.Token)

less := lineLess

if block.Token[0] == "retract" {

less = lineRetractLess

}

sort.SliceStable(block.Line, func(i, j int) bool {

return less(block.Line[i], block.Line[j])

})

}

}

// removeDups removes duplicate exclude and replace directives.

//

// Earlier exclude directives take priority.

//

// Later replace directives take priority.

//

// require directives are not de-duplicated. That's left up to higher-level

// logic (MVS).

//

// retract directives are not de-duplicated since comments are

// meaningful, and versions may be retracted multiple times.

func (f *File) removeDups() {

have := make(map[module.Version]bool)

kill := make(map[*Line]bool)

// Remove duplicate excludes.

haveExclude := make(map[module.Version]bool)

for _, x := range f.Exclude {

if have[x.Mod] {

if haveExclude[x.Mod] {

kill[x.Syntax] = true

continue

}

have[x.Mod] = true

haveExclude[x.Mod] = true

}

var excl []*Exclude

for _, x := range f.Exclude {

@@ -817,15 +981,16 @@ func (f *File) removeDups() {

}

f.Exclude = excl

have = make(map[module.Version]bool)

// Remove duplicate replacements.

// Later replacements take priority over earlier ones.

haveReplace := make(map[module.Version]bool)

for i := len(f.Replace) - 1; i >= 0; i-- {

x := f.Replace[i]

if have[x.Old] {

if haveReplace[x.Old] {

kill[x.Syntax] = true

continue

}

have[x.Old] = true

haveReplace[x.Old] = true

}

var repl []*Replace

for _, x := range f.Replace {

@@ -835,6 +1000,9 @@ func (f *File) removeDups() {

}

f.Replace = repl

// Duplicate require and retract directives are not removed.

// Drop killed statements from the syntax tree.

var stmts []Expr

for _, stmt := range f.Syntax.Stmt {

switch stmt := stmt.(type) {

@@ -858,3 +1026,38 @@ func (f *File) removeDups() {

}

f.Syntax.Stmt = stmts

}

// lineLess returns whether li should be sorted before lj. It sorts

// lexicographically without assigning any special meaning to tokens.

func lineLess(li, lj *Line) bool {

for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {

if li.Token[k] != lj.Token[k] {

return li.Token[k] < lj.Token[k]

}

}

return len(li.Token) < len(lj.Token)

}

// lineRetractLess returns whether li should be sorted before lj for lines in

// a "retract" block. It treats each line as a version interval. Single versions

// are compared as if they were intervals with the same low and high version.

// Intervals are sorted in descending order, first by low version, then by

// high version, using semver.Compare.

func lineRetractLess(li, lj *Line) bool {

interval := func(l *Line) VersionInterval {

if len(l.Token) == 1 {

return VersionInterval{Low: l.Token[0], High: l.Token[0]}

} else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" {

return VersionInterval{Low: l.Token[1], High: l.Token[3]}

} else {

// Line in unknown format. Treat as an invalid version.

return VersionInterval{}

}

}

vii := interval(li)

vij := interval(lj)

if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 {

return cmp > 0

}

return semver.Compare(vii.High, vij.High) > 0

}


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK