// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package modconv import ( "fmt" "internal/lazyregexp" "net/url" "path" "strconv" "strings" "golang.org/x/mod/modfile" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) func ParseGopkgLock(file string, data []byte) (*modfile.File, error) { type pkg struct { Path string Version string Source string } mf := new(modfile.File) var list []pkg var r *pkg for lineno, line := range strings.Split(string(data), "\n") { lineno++ if i := strings.Index(line, "#"); i >= 0 { line = line[:i] } line = strings.TrimSpace(line) if line == "[[projects]]" { list = append(list, pkg{}) r = &list[len(list)-1] continue } if strings.HasPrefix(line, "[") { r = nil continue } if r == nil { continue } before, after, found := strings.Cut(line, "=") if !found { continue } key := strings.TrimSpace(before) val := strings.TrimSpace(after) if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' { q, err := strconv.Unquote(val) // Go unquoting, but close enough for now if err != nil { return nil, fmt.Errorf("%s:%d: invalid quoted string: %v", file, lineno, err) } val = q } switch key { case "name": r.Path = val case "source": r.Source = val case "revision", "version": // Note: key "version" should take priority over "revision", // and it does, because dep writes toml keys in alphabetical order, // so we see version (if present) second. if key == "version" { if !semver.IsValid(val) || semver.Canonical(val) != val { break } } r.Version = val } } for _, r := range list { if r.Path == "" || r.Version == "" { return nil, fmt.Errorf("%s: empty [[projects]] stanza (%s)", file, r.Path) } mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: r.Path, Version: r.Version}}) if r.Source != "" { // Convert "source" to import path, such as // git@test.com:x/y.git and https://test.com/x/y.git. // We get "test.com/x/y" at last. source, err := decodeSource(r.Source) if err != nil { return nil, err } old := module.Version{Path: r.Path, Version: r.Version} new := module.Version{Path: source, Version: r.Version} mf.Replace = append(mf.Replace, &modfile.Replace{Old: old, New: new}) } } return mf, nil } var scpSyntaxReg = lazyregexp.New(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`) func decodeSource(source string) (string, error) { var u *url.URL var p string if m := scpSyntaxReg.FindStringSubmatch(source); m != nil { // Match SCP-like syntax and convert it to a URL. // Eg, "git@github.com:user/repo" becomes // "ssh://git@github.com/user/repo". u = &url.URL{ Scheme: "ssh", User: url.User(m[1]), Host: m[2], Path: "/" + m[3], } } else { var err error u, err = url.Parse(source) if err != nil { return "", fmt.Errorf("%q is not a valid URI", source) } } // If no scheme was passed, then the entire path will have been put into // u.Path. Either way, construct the normalized path correctly. if u.Host == "" { p = source } else { p = path.Join(u.Host, u.Path) } p = strings.TrimSuffix(p, ".git") p = strings.TrimSuffix(p, ".hg") return p, nil }