Source file src/cmd/go/internal/modconv/dep.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modconv
     6  
     7  import (
     8  	"fmt"
     9  	"internal/lazyregexp"
    10  	"net/url"
    11  	"path"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"golang.org/x/mod/modfile"
    16  	"golang.org/x/mod/module"
    17  	"golang.org/x/mod/semver"
    18  )
    19  
    20  func ParseGopkgLock(file string, data []byte) (*modfile.File, error) {
    21  	type pkg struct {
    22  		Path    string
    23  		Version string
    24  		Source  string
    25  	}
    26  	mf := new(modfile.File)
    27  	var list []pkg
    28  	var r *pkg
    29  	for lineno, line := range strings.Split(string(data), "\n") {
    30  		lineno++
    31  		if i := strings.Index(line, "#"); i >= 0 {
    32  			line = line[:i]
    33  		}
    34  		line = strings.TrimSpace(line)
    35  		if line == "[[projects]]" {
    36  			list = append(list, pkg{})
    37  			r = &list[len(list)-1]
    38  			continue
    39  		}
    40  		if strings.HasPrefix(line, "[") {
    41  			r = nil
    42  			continue
    43  		}
    44  		if r == nil {
    45  			continue
    46  		}
    47  		i := strings.Index(line, "=")
    48  		if i < 0 {
    49  			continue
    50  		}
    51  		key := strings.TrimSpace(line[:i])
    52  		val := strings.TrimSpace(line[i+1:])
    53  		if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
    54  			q, err := strconv.Unquote(val) // Go unquoting, but close enough for now
    55  			if err != nil {
    56  				return nil, fmt.Errorf("%s:%d: invalid quoted string: %v", file, lineno, err)
    57  			}
    58  			val = q
    59  		}
    60  		switch key {
    61  		case "name":
    62  			r.Path = val
    63  		case "source":
    64  			r.Source = val
    65  		case "revision", "version":
    66  			// Note: key "version" should take priority over "revision",
    67  			// and it does, because dep writes toml keys in alphabetical order,
    68  			// so we see version (if present) second.
    69  			if key == "version" {
    70  				if !semver.IsValid(val) || semver.Canonical(val) != val {
    71  					break
    72  				}
    73  			}
    74  			r.Version = val
    75  		}
    76  	}
    77  	for _, r := range list {
    78  		if r.Path == "" || r.Version == "" {
    79  			return nil, fmt.Errorf("%s: empty [[projects]] stanza (%s)", file, r.Path)
    80  		}
    81  		mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: r.Path, Version: r.Version}})
    82  
    83  		if r.Source != "" {
    84  			// Convert "source" to import path, such as
    85  			// git@test.com:x/y.git and https://test.com/x/y.git.
    86  			// We get "test.com/x/y" at last.
    87  			source, err := decodeSource(r.Source)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  			old := module.Version{Path: r.Path, Version: r.Version}
    92  			new := module.Version{Path: source, Version: r.Version}
    93  			mf.Replace = append(mf.Replace, &modfile.Replace{Old: old, New: new})
    94  		}
    95  	}
    96  	return mf, nil
    97  }
    98  
    99  var scpSyntaxReg = lazyregexp.New(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
   100  
   101  func decodeSource(source string) (string, error) {
   102  	var u *url.URL
   103  	var p string
   104  	if m := scpSyntaxReg.FindStringSubmatch(source); m != nil {
   105  		// Match SCP-like syntax and convert it to a URL.
   106  		// Eg, "git@github.com:user/repo" becomes
   107  		// "ssh://git@github.com/user/repo".
   108  		u = &url.URL{
   109  			Scheme: "ssh",
   110  			User:   url.User(m[1]),
   111  			Host:   m[2],
   112  			Path:   "/" + m[3],
   113  		}
   114  	} else {
   115  		var err error
   116  		u, err = url.Parse(source)
   117  		if err != nil {
   118  			return "", fmt.Errorf("%q is not a valid URI", source)
   119  		}
   120  	}
   121  
   122  	// If no scheme was passed, then the entire path will have been put into
   123  	// u.Path. Either way, construct the normalized path correctly.
   124  	if u.Host == "" {
   125  		p = source
   126  	} else {
   127  		p = path.Join(u.Host, u.Path)
   128  	}
   129  	p = strings.TrimSuffix(p, ".git")
   130  	p = strings.TrimSuffix(p, ".hg")
   131  	return p, nil
   132  }
   133  

View as plain text