github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/fetch_repo/module.go (about)

     1  /* Copyright 2019 The Bazel Authors. All rights reserved.
     2  
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package main
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  
    22  	"golang.org/x/mod/sumdb/dirhash"
    23  )
    24  
    25  func fetchModule(dest, importpath, version, sum string) error {
    26  	// Check that version is a complete semantic version or pseudo-version.
    27  	if _, ok := parse(version); !ok {
    28  		return fmt.Errorf("%q is not a valid semantic version", version)
    29  	} else if isSemverPrefix(version) {
    30  		return fmt.Errorf("-version must be a complete semantic version. %q is a prefix.", version)
    31  	}
    32  
    33  	// Download the module. In Go 1.11, this command must be run in a module,
    34  	// so we create a dummy module in the current directory (which should be
    35  	// empty).
    36  	w, err := os.OpenFile("go.mod", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666)
    37  	if err != nil {
    38  		return fmt.Errorf("error creating temporary go.mod: %v", err)
    39  	}
    40  	_, err = fmt.Fprintln(w, "module example.com/temporary/module/for/fetch_repo/download")
    41  	if err != nil {
    42  		w.Close()
    43  		return fmt.Errorf("error writing temporary go.mod: %v", err)
    44  	}
    45  	if err := w.Close(); err != nil {
    46  		return fmt.Errorf("error closing temporary go.mod: %v", err)
    47  	}
    48  
    49  	dl := GoModDownloadResult{}
    50  	err = runGoModDownload(&dl, dest, importpath, version)
    51  	os.Remove("go.mod")
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	// Copy the module to the destination.
    57  	if err := copyTree(dest, dl.Dir); err != nil {
    58  		return fmt.Errorf("failed copying repo: %w", err)
    59  	}
    60  
    61  	// Verify sum of the directory itself against the go.sum.
    62  	repoSum, err := dirhash.HashDir(dest, importpath+"@"+version, dirhash.Hash1)
    63  	if err != nil {
    64  		return fmt.Errorf("failed computing sum: %w", err)
    65  	}
    66  
    67  	if repoSum != sum {
    68  		if goModCache := os.Getenv("GOMODCACHE"); goModCache != "" {
    69  			return fmt.Errorf("resulting module with sum %s; expected sum %s, Please try clearing your module cache directory %q", repoSum, sum, goModCache)
    70  		}
    71  		return fmt.Errorf("resulting module with sum %s; expected sum %s", repoSum, sum)
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // semantic version parsing functions below this point were copied from
    78  // cmd/go/internal/semver and cmd/go/internal/modload at go1.12beta2.
    79  
    80  // parsed returns the parsed form of a semantic version string.
    81  type parsed struct {
    82  	major      string
    83  	minor      string
    84  	patch      string
    85  	short      string
    86  	prerelease string
    87  	build      string
    88  	err        string
    89  }
    90  
    91  func parse(v string) (p parsed, ok bool) {
    92  	if v == "" || v[0] != 'v' {
    93  		p.err = "missing v prefix"
    94  		return
    95  	}
    96  	p.major, v, ok = parseInt(v[1:])
    97  	if !ok {
    98  		p.err = "bad major version"
    99  		return
   100  	}
   101  	if v == "" {
   102  		p.minor = "0"
   103  		p.patch = "0"
   104  		p.short = ".0.0"
   105  		return
   106  	}
   107  	if v[0] != '.' {
   108  		p.err = "bad minor prefix"
   109  		ok = false
   110  		return
   111  	}
   112  	p.minor, v, ok = parseInt(v[1:])
   113  	if !ok {
   114  		p.err = "bad minor version"
   115  		return
   116  	}
   117  	if v == "" {
   118  		p.patch = "0"
   119  		p.short = ".0"
   120  		return
   121  	}
   122  	if v[0] != '.' {
   123  		p.err = "bad patch prefix"
   124  		ok = false
   125  		return
   126  	}
   127  	p.patch, v, ok = parseInt(v[1:])
   128  	if !ok {
   129  		p.err = "bad patch version"
   130  		return
   131  	}
   132  	if len(v) > 0 && v[0] == '-' {
   133  		p.prerelease, v, ok = parsePrerelease(v)
   134  		if !ok {
   135  			p.err = "bad prerelease"
   136  			return
   137  		}
   138  	}
   139  	if len(v) > 0 && v[0] == '+' {
   140  		p.build, v, ok = parseBuild(v)
   141  		if !ok {
   142  			p.err = "bad build"
   143  			return
   144  		}
   145  	}
   146  	if v != "" {
   147  		p.err = "junk on end"
   148  		ok = false
   149  		return
   150  	}
   151  	ok = true
   152  	return
   153  }
   154  
   155  func parseInt(v string) (t, rest string, ok bool) {
   156  	if v == "" {
   157  		return
   158  	}
   159  	if v[0] < '0' || '9' < v[0] {
   160  		return
   161  	}
   162  	i := 1
   163  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
   164  		i++
   165  	}
   166  	if v[0] == '0' && i != 1 {
   167  		return
   168  	}
   169  	return v[:i], v[i:], true
   170  }
   171  
   172  func parsePrerelease(v string) (t, rest string, ok bool) {
   173  	// "A pre-release version MAY be denoted by appending a hyphen and
   174  	// a series of dot separated identifiers immediately following the patch version.
   175  	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
   176  	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
   177  	if v == "" || v[0] != '-' {
   178  		return
   179  	}
   180  	i := 1
   181  	start := 1
   182  	for i < len(v) && v[i] != '+' {
   183  		if !isIdentChar(v[i]) && v[i] != '.' {
   184  			return
   185  		}
   186  		if v[i] == '.' {
   187  			if start == i || isBadNum(v[start:i]) {
   188  				return
   189  			}
   190  			start = i + 1
   191  		}
   192  		i++
   193  	}
   194  	if start == i || isBadNum(v[start:i]) {
   195  		return
   196  	}
   197  	return v[:i], v[i:], true
   198  }
   199  
   200  func parseBuild(v string) (t, rest string, ok bool) {
   201  	if v == "" || v[0] != '+' {
   202  		return
   203  	}
   204  	i := 1
   205  	start := 1
   206  	for i < len(v) {
   207  		if !isIdentChar(v[i]) && v[i] != '.' {
   208  			return
   209  		}
   210  		if v[i] == '.' {
   211  			if start == i {
   212  				return
   213  			}
   214  			start = i + 1
   215  		}
   216  		i++
   217  	}
   218  	if start == i {
   219  		return
   220  	}
   221  	return v[:i], v[i:], true
   222  }
   223  
   224  func isIdentChar(c byte) bool {
   225  	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
   226  }
   227  
   228  func isBadNum(v string) bool {
   229  	i := 0
   230  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
   231  		i++
   232  	}
   233  	return i == len(v) && i > 1 && v[0] == '0'
   234  }
   235  
   236  // isSemverPrefix reports whether v is a semantic version prefix: v1 or  v1.2 (not v1.2.3).
   237  // The caller is assumed to have checked that semver.IsValid(v) is true.
   238  func isSemverPrefix(v string) bool {
   239  	dots := 0
   240  	for i := 0; i < len(v); i++ {
   241  		switch v[i] {
   242  		case '-', '+':
   243  			return false
   244  		case '.':
   245  			dots++
   246  			if dots >= 2 {
   247  				return false
   248  			}
   249  		}
   250  	}
   251  	return true
   252  }