diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2da54d3..456221f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - go-version: [ 1.22.x ] + go-version: [ 1.18.x, 1.19.x ] # Set some variables per OS, usable via ${{ matrix.VAR }} # XK6_BIN_PATH: the path to the compiled k6 binary, for artifact publishing @@ -100,7 +100,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.22.x + go-version: 1.19.x check-latest: true - name: Retrieve golangci-lint version run: | diff --git a/.golangci.yml b/.golangci.yml index f0d95ab..fd22463 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,4 @@ -# v1.59.1 +# v1.50.1 # Please don't remove the first line. It uses in CI to determine the golangci version linters-settings: errcheck: diff --git a/README.md b/README.md index ea885b9..a2345e1 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,6 @@ xk6 build [] - `--with` can be used multiple times to add extensions by specifying the Go module name and optionally its version, similar to `go get`. Module name is required, but specific version and/or local replacement are optional. For an up-to-date list of k6 extensions, head to our [extensions page](https://k6.io/docs/extensions/). - `--replace` can be used multiple times to add replacements by specifying the Go module name and the replacement module, similar to `go mod edit -replace=`. Version of the replacement can be specified with the `@version` suffix in the replacement path. -Versions can be anything compatible with `go get`. - Examples: ```bash @@ -169,16 +167,36 @@ xk6 run -u 10 -d 10s test.js The race detector can be enabled by setting the env variable `XK6_RACE_DETECTOR=1` or through the `XK6_BUILD_FLAGS` env variable. + +## Library usage + +```go +builder := xk6.Builder{ + K6Version: "v0.35.0", + Extensions: []xk6.Dependency{ + { + PackagePath: "github.com/grafana/xk6-browser", + Version: "v0.1.1", + }, + }, +} +err := builder.Build(context.Background(), "./k6") +``` + +Versions can be anything compatible with `go get`. + + ## Environment variables Because the subcommands and flags are constrained to benefit rapid extension prototyping, xk6 does read some environment variables to take cues for its behavior and/or configuration when there is no room for flags. - `K6_VERSION` sets the version of k6 to build. -- `XK6_BUILD_FLAGS` sets any go build flags if needed. Defaults to '-ldflags=-w -s -trim'. -- `XK6_RACE_DETECTOR=1` enables the Go race detector in the build. Forces `GCO_ENABLED=1`. +- `XK6_BUILD_FLAGS` sets any go build flags if needed. Defaults to '-ldflags=-w -s'. +- `XK6_RACE_DETECTOR=1` enables the Go race detector in the build. - `XK6_SKIP_CLEANUP=1` causes xk6 to leave build artifacts on disk after exiting. - `XK6_K6_REPO` optionally sets the path to the main k6 repository. This is useful when building with k6 forks. + ## Keeping dependencies in sync We recommend extension maintainers to keep dependencies in common with k6 core in the same version k6 core uses. This guarantees binary compatibility of the JS runtime, and ensures uses will not have to face unforeseen build-time errors when compiling several extensions together with xk6. @@ -189,26 +207,6 @@ The [`go-depsync`](https://github.com/grafana/go-depsync/) tool can check for th /your/extension$ go-depsync --parent go.k6.io/k6 ``` -## Library usage - -> !Breaking change: since v0.14.0 `xk6.Builder.Build` function no longer reads environment variables to complete missing attributes. Use `xk6.FromOSEnv()` to create a builder from environment variables and then complete or override attributes as needed. - -```go -// create builder with defaults from environment variables -builder := xk6.FromOSEnv() - -// complete/override attributes -builder.K6Version = "v0.35.0", -builder.Extensions = []xk6.Dependency{ - { - PackagePath: "github.com/grafana/xk6-browser", - Version: "v0.1.1", - }, -} - -err := builder.Build(context.Background(), log. "./k6") -``` - --- > This project originally forked from the [xcaddy](https://github.com/caddyserver/xcaddy) project. **Thank you!** diff --git a/builder.go b/builder.go index 8e684b9..e4eef59 100644 --- a/builder.go +++ b/builder.go @@ -17,18 +17,17 @@ package xk6 import ( "context" "fmt" - "log/slog" + "log" "os" + "path" "path/filepath" + "regexp" "runtime" + "strconv" "strings" "time" - "github.com/grafana/k6foundry" -) - -const ( - defaultBuildFlags = "-ldflags='-w -s' -trimpath" + "github.com/Masterminds/semver/v3" ) // Builder can produce a custom k6 build with the @@ -43,134 +42,102 @@ type Builder struct { TimeoutBuild time.Duration `json:"timeout_build,omitempty"` RaceDetector bool `json:"race_detector,omitempty"` SkipCleanup bool `json:"skip_cleanup,omitempty"` - BuildFlags string `json:"build_flags,omitempty"` -} - -// FromOSEnv creates a Builder from environment variables: -// GOARCH, GOOS, GOARM defines builder's target platform -// K6_VERSION sets the version of k6 to build. -// XK6_BUILD_FLAGS sets any go build flags if needed. Defaults to '-ldflags=-w -s -trim'. -// XK6_RACE_DETECTOR enables the Go race detector in the build. Forces CGO_ENABLED=1 -// XK6_SKIP_CLEANUP causes xk6 to leave build artifacts on disk after exiting. -// XK6_K6_REPO sets the path to the main k6 repository. This is useful when building with k6 forks -func FromOSEnv() Builder { - env := map[string]string{} - for _, arg := range os.Environ() { - parts := strings.SplitN(arg, "=", 2) - env[parts[0]] = parts[1] - } - return parseEnv(env) -} - -func parseEnv(env map[string]string) Builder { - return Builder{ - Compile: Compile{ - Platform: Platform{ - OS: env["GOOS"], - Arch: env["GOARCH"], - ARM: env["GOARM"], - }, - }, - K6Version: env["K6_VERSION"], - K6Repo: env["XK6_K6_REPO"], - RaceDetector: env["XK6_RACE_DETECTOR"] == "1", - SkipCleanup: env["XK6_SKIP_CLEANUP"] == "1", - BuildFlags: envOrDefaultValue(env, "XK6_BUILD_FLAGS", defaultBuildFlags), - } } // Build builds k6 at the configured version with the // configured extensions and writes a binary at outputFile. -func (b Builder) Build(ctx context.Context, log *slog.Logger, outfile string) error { - if outfile == "" { +func (b Builder) Build(ctx context.Context, outputFile string) error { + if outputFile == "" { return fmt.Errorf("output file path is required") } + // the user's specified output file might be relative, and + // because the `go build` command is executed in a different, + // temporary folder, we convert the user's input to an + // absolute path so it goes the expected place + absOutputFile, err := filepath.Abs(outputFile) + if err != nil { + return err + } // set some defaults from the environment, if applicable if b.OS == "" { - b.OS = runtime.GOOS + b.OS = os.Getenv("GOOS") } if b.Arch == "" { - b.Arch = runtime.GOARCH + b.Arch = os.Getenv("GOARCH") + } + if b.ARM == "" { + b.ARM = os.Getenv("GOARM") } - // We are not passing all the current environment anymore! ONLY the GO environment - // env := os.Environ() - env := map[string]string{ - "GOARM": b.ARM, + // prepare the build environment + buildEnv, err := b.newEnvironment(ctx) + if err != nil { + return err } + defer buildEnv.Close() + + // prepare the environment for the go command; for + // the most part we want it to inherit our current + // environment, with a few customizations + env := os.Environ() + env = setEnv(env, "GOOS="+b.OS) + env = setEnv(env, "GOARCH="+b.Arch) + env = setEnv(env, "GOARM="+b.ARM) raceArg := "-race" // trim debug symbols by default - if (b.RaceDetector || strings.Contains(b.BuildFlags, raceArg)) && !b.Compile.Cgo { - log.Warn("Enabling cgo because it is required by the race detector") - b.Compile.Cgo = true - } - env["CGO_ENABLED"] = b.Compile.CgoEnabled() + buildFlags := b.osEnvOrDefaultValue("XK6_BUILD_FLAGS", "-ldflags='-w -s' -trimpath") - log.Info("Building k6") + buildFlagsSlice := buildCommandArgs(buildFlags, absOutputFile) - opts := k6foundry.NativeBuilderOpts{ - GoOpts: k6foundry.GoOpts{ - GoGetTimeout: b.TimeoutGet, - GOBuildTimeout: b.TimeoutBuild, - CopyGoEnv: true, - Env: env, - }, - SkipCleanup: b.SkipCleanup, - Stdout: os.Stdout, - Stderr: os.Stderr, - Logger: log, + if (b.RaceDetector || strings.Contains(buildFlags, raceArg)) && !b.Compile.Cgo { + log.Println("[WARNING] Enabling cgo because it is required by the race detector") + b.Compile.Cgo = true } + env = setEnv(env, fmt.Sprintf("CGO_ENABLED=%s", b.Compile.CgoEnabled())) - k6b, err := k6foundry.NewNativeBuilder(ctx, opts) - if err != nil { + log.Println("[INFO] Building k6") + + if err := buildEnv.execGoModTidy(ctx); err != nil { return err } - // the user's specified output file might be relative, and - // because the `go build` command is executed in a different, - // temporary folder, we convert the user's input to an - // absolute path so it goes the expected place - absOutputFile, err := filepath.Abs(outfile) - if err != nil { - return err + // compile + cmd := buildEnv.newCommand("go", + buildFlagsSlice..., + ) + // dont add raceArg again if it already in place + if b.RaceDetector && !strings.Contains(buildFlags, raceArg) { + cmd.Args = append(cmd.Args, raceArg) } - outFile, err := os.OpenFile(absOutputFile, os.O_WRONLY|os.O_CREATE, 0o777) //nolint:gosec + cmd.Env = env + err = buildEnv.runCommand(ctx, cmd, b.TimeoutBuild) if err != nil { return err } - defer outFile.Close() //nolint:errcheck - platform, err := k6foundry.ParsePlatform(b.OS + "/" + b.Arch) - if err != nil { - return err - } + log.Printf("[INFO] Build complete: %s", outputFile) - mods := []k6foundry.Module{} - for _, e := range b.Extensions { - mod := k6foundry.Module{Path: e.PackagePath, Version: e.Version} - if mod.Version == "" { - mod.Version = "latest" - } - mods = append(mods, mod) - } - for _, r := range b.Replacements { - // parse again the replacement, ignore the error because it was already parsed - mod, err := k6foundry.ParseModule(fmt.Sprintf("%s=%s", r.Old.String(), r.New.String())) - if err != nil { - return err - } - mods = append(mods, mod) - } + return nil +} - k6Version := b.K6Version - if k6Version == "" { - k6Version = "latest" +// setEnv sets an environment variable-value pair in +// env, overriding an existing variable if it already +// exists. The env slice is one such as is returned +// by os.Environ(), and set must also have the form +// of key=value. +func setEnv(env []string, set string) []string { + parts := strings.SplitN(set, "=", 2) + key := parts[0] + for i := 0; i < len(env); i++ { + if strings.HasPrefix(env[i], key+"=") { + env[i] = set + return env + } } - _, err = k6b.Build(ctx, platform, k6Version, mods, buildCommandArgs(b.BuildFlags), outFile) - return err + return append(env, set) } // Dependency pairs a Go module path with a version. @@ -214,8 +181,91 @@ func NewReplace(old, new string) Replace { } } -func envOrDefaultValue(env map[string]string, name, defaultValue string) string { - s, ok := env[name] +// newTempFolder creates a new folder in a temporary location. +// It is the caller's responsibility to remove the folder when finished. +func newTempFolder() (string, error) { + var parentDir string + if runtime.GOOS == "darwin" { + // After upgrading to macOS High Sierra, Caddy builds mysteriously + // started missing the embedded version information that -ldflags + // was supposed to produce. But it only happened on macOS after + // upgrading to High Sierra, and it didn't happen with the usual + // `go run build.go` -- only when using a buildenv. Bug in git? + // Nope. Not a bug in Go 1.10 either. Turns out it's a breaking + // change in macOS High Sierra. When the GOPATH of the buildenv + // was set to some other folder, like in the $HOME dir, it worked + // fine. Only within $TMPDIR it broke. The $TMPDIR folder is inside + // /var, which is symlinked to /private/var, which is mounted + // with noexec. I don't understand why, but evidently that + // makes -ldflags of `go build` not work. Bizarre. + // The solution, I guess, is to just use our own "temp" dir + // outside of /var. Sigh... as long as it still gets cleaned up, + // I guess it doesn't matter too much. + // See: https://github.com/caddyserver/caddy/issues/2036 + // and https://twitter.com/mholt6/status/978345803365273600 (thread) + // (using an absolute path prevents problems later when removing this + // folder if the CWD changes) + var err error + parentDir, err = filepath.Abs(".") + if err != nil { + return "", err + } + } + ts := time.Now().Format(yearMonthDayHourMin) + return os.MkdirTemp(parentDir, fmt.Sprintf("buildenv_%s.", ts)) +} + +// versionedModulePath helps enforce Go Module's Semantic Import Versioning (SIV) by +// returning the form of modulePath with the major component of moduleVersion added, +// if > 1. For example, inputs of "foo" and "v1.0.0" will return "foo", but inputs +// of "foo" and "v2.0.0" will return "foo/v2", for use in Go imports and go commands. +// Inputs that conflict, like "foo/v2" and "v3.1.0" are an error. This function +// returns the input if the moduleVersion is not a valid semantic version string. +// If moduleVersion is empty string, the input modulePath is returned without error. +func versionedModulePath(modulePath, moduleVersion string) (string, error) { + if moduleVersion == "" { + return modulePath, nil + } + ver, err := semver.StrictNewVersion(strings.TrimPrefix(moduleVersion, "v")) + if err != nil { + // only return the error if we know they were trying to use a semantic version + // (could have been a commit SHA or something) + if strings.HasPrefix(moduleVersion, "v") { + return "", fmt.Errorf("%s: %v", moduleVersion, err) + } + return modulePath, nil + } + major := ver.Major() + + // see if the module path has a major version at the end (SIV) + matches := moduleVersionRegexp.FindStringSubmatch(modulePath) + if len(matches) == 2 { + modPathVer, err := strconv.Atoi(matches[1]) + if err != nil { + return "", fmt.Errorf("this error should be impossible, but module path %s has bad version: %v", modulePath, err) + } + if modPathVer != int(major) { + return "", fmt.Errorf("versioned module path (%s) and requested module major version (%d) diverge", modulePath, major) + } + } else if major > 1 { + modulePath += fmt.Sprintf("/v%d", major) + } + + return path.Clean(modulePath), nil +} + +var moduleVersionRegexp = regexp.MustCompile(`.+/v(\d+)$`) + +const ( + // yearMonthDayHourMin is the date format + // used for temporary folder paths. + yearMonthDayHourMin = "2006-01-02-1504" + + defaultK6ModulePath = "go.k6.io/k6" +) + +func (b Builder) osEnvOrDefaultValue(name, defaultValue string) string { + s, ok := os.LookupEnv(name) if !ok { return defaultValue } @@ -225,8 +275,9 @@ func envOrDefaultValue(env map[string]string, name, defaultValue string) string // buildCommandArgs parses the build flags passed by environment variable XK6_BUILD_FLAGS // or the default values when no value for it is given // so we may pass args separately to newCommand() -func buildCommandArgs(buildFlags string) []string { +func buildCommandArgs(buildFlags, absOutputFile string) []string { buildFlagsSlice := make([]string, 0, 10) + buildFlagsSlice = append(buildFlagsSlice, "build", "-o", absOutputFile) tmp := []string{} sb := &strings.Builder{} diff --git a/builder_test.go b/builder_test.go index 6a827f9..e4fe523 100644 --- a/builder_test.go +++ b/builder_test.go @@ -91,136 +91,50 @@ func TestNewReplace(t *testing.T) { } } - -func TestParseEnv(t *testing.T) { - testCases := []struct { - title string - env map[string]string - expect Builder - expectErr string +func TestBuildCommandArgs(t *testing.T) { + t.Parallel() + tests := []struct { + buildFlags string + want []string }{ { - title: "parse defaults", - env: map[string]string{}, - expect: Builder{ - Compile: Compile{ - Cgo: false, - Platform: Platform{ - Arch: "", - OS: "", - ARM: "", - }, - }, - K6Repo: "", - K6Version: "", - BuildFlags: defaultBuildFlags, - RaceDetector: false, - SkipCleanup: false, - Extensions: nil, - Replacements: nil, + buildFlags: "", + want: []string{ + "build", "-o", "binfile", }, }, { - title: "parse k6 version", - env: map[string]string{ - "K6_VERSION": "v0.0.0", - }, - expect: Builder{ - Compile: Compile{ - Cgo: false, - Platform: Platform{ - Arch: "", - OS: "", - ARM: "", - }, - }, - K6Repo: "", - K6Version: "v0.0.0", - BuildFlags: defaultBuildFlags, - RaceDetector: false, - SkipCleanup: false, - Extensions: nil, - Replacements: nil, + buildFlags: "-ldflags='-w -s'", + want: []string{ + "build", "-o", "binfile", "-ldflags=-w -s", }, }, { - title: "parse k6 repo", - env: map[string]string{ - "XK6_K6_REPO": "github.com/another/repo", - }, - expect: Builder{ - Compile: Compile{ - Cgo: false, - Platform: Platform{ - Arch: "", - OS: "", - ARM: "", - }, - }, - K6Repo: "github.com/another/repo", - K6Version: "", - BuildFlags: defaultBuildFlags, - RaceDetector: false, - SkipCleanup: false, - Extensions: nil, - Replacements: nil, + buildFlags: "-race -buildvcs=false", + want: []string{ + "build", "-o", "binfile", "-race", "-buildvcs=false", }, }, { - title: "parse GO environment variables", - env: map[string]string{ - "GOARCH": "amd64", - "GOOS": "linux", - }, - expect: Builder{ - Compile: Compile{ - Cgo: false, - Platform: Platform{ - Arch: "amd64", - OS: "linux", - ARM: "", - }, - }, - K6Repo: "", - K6Version: "", - BuildFlags: defaultBuildFlags, - RaceDetector: false, - SkipCleanup: false, - Extensions: nil, - Replacements: nil, + buildFlags: `-buildvcs=false -ldflags="-s -w" -race`, + want: []string{ + "build", "-o", "binfile", "-buildvcs=false", "-ldflags=-s -w", "-race", }, }, { - title: "parse build opts", - env: map[string]string{ - "XK6_BUILD_FLAGS": "-buildvcs", - }, - expect: Builder{ - Compile: Compile{ - Cgo: false, - Platform: Platform{ - Arch: "", - OS: "", - ARM: "", - }, - }, - K6Repo: "", - K6Version: "", - BuildFlags: "-buildvcs", - RaceDetector: false, - SkipCleanup: false, - Extensions: nil, - Replacements: nil, + buildFlags: `-ldflags="-s -w" -race -buildvcs=false`, + want: []string{ + "build", "-o", "binfile", "-ldflags=-s -w", "-race", "-buildvcs=false", }, }, } - for _, tc := range testCases { - t.Run(tc.title, func(t *testing.T) { + for _, tt := range tests { + tt := tt + t.Run(tt.buildFlags, func(t *testing.T) { t.Parallel() - got := parseEnv(tc.env) - if !reflect.DeepEqual(got, tc.expect) { - t.Errorf("expected %v, got %v", tc.expect, got) + if got := buildCommandArgs(tt.buildFlags, "binfile"); !reflect.DeepEqual(got, tt.want) { + t.Errorf("buildCommandArgs() = %v, want %v", got, tt.want) } }) } diff --git a/cmd/xk6/main.go b/cmd/xk6/main.go index 1906bc8..57b5a99 100644 --- a/cmd/xk6/main.go +++ b/cmd/xk6/main.go @@ -4,19 +4,20 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + package main import ( "context" "fmt" - "log/slog" + "log" "os" "os/exec" "os/signal" @@ -28,60 +29,124 @@ import ( "go.k6.io/xk6" ) -type BuildOps struct { - K6Version string - Extensions []xk6.Dependency - Replacements []xk6.Replace - OutFile string - OutputOverride bool -} +var ( + k6Version = os.Getenv("K6_VERSION") + k6Repo = os.Getenv("XK6_K6_REPO") + raceDetector = os.Getenv("XK6_RACE_DETECTOR") == "1" + skipCleanup = os.Getenv("XK6_SKIP_CLEANUP") == "1" +) func main() { - log := slog.New( - slog.NewTextHandler( - os.Stderr, - &slog.HandlerOptions{ - Level: slog.LevelDebug, - }, - ), - ) - ctx, cancel := context.WithCancel(context.Background()) defer cancel() - go trapSignals(ctx, log, cancel) + go trapSignals(ctx, cancel) if len(os.Args) > 1 && os.Args[1] == "build" { - if err := runBuild(ctx, log, os.Args[2:]); err != nil { - log.Error(fmt.Sprintf("build error %v", err)) + if err := runBuild(ctx, os.Args[2:]); err != nil { + log.Fatalf("[ERROR] %v", err) } return } - if err := runDev(ctx, log, os.Args[1:]); err != nil { - log.Error(fmt.Sprintf("run error %v", err)) + if err := runDev(ctx, os.Args[1:]); err != nil { + log.Fatalf("[ERROR] %v", err) } } -func runBuild(ctx context.Context, log *slog.Logger, args []string) error { - opts, err := parseBuildOpts(args) - if err != nil { - return fmt.Errorf("parsing options %v", err) +func runBuild(ctx context.Context, args []string) error { + // parse the command line args... rather primitively + var argK6Version, output string + var outputOverride bool + var extensions []xk6.Dependency + var replacements []xk6.Replace + for i := 0; i < len(args); i++ { + switch args[i] { + case "--with": + if i == len(args)-1 { + return fmt.Errorf("expected value after --with flag") + } + i++ + mod, ver, repl, err := splitWith(args[i]) + if err != nil { + return err + } + mod = strings.TrimSuffix(mod, "/") // easy to accidentally leave a trailing slash if pasting from a URL, but is invalid for Go modules + extensions = append(extensions, xk6.Dependency{ + PackagePath: mod, + Version: ver, + }) + if repl != "" { + repl, err = expandPath(repl) + if err != nil { + return err + } + replacements = append(replacements, xk6.NewReplace(mod, repl)) + } + + case "--replace": + if i == len(args)-1 { + return fmt.Errorf("expected value after --replace flag") + } + i++ + mod, _, repl, err := splitWith(args[i]) + if err != nil { + return err + } + if repl == "" { + return fmt.Errorf("replace value must be of format 'module=replace' or 'module=replace@version'") + } + // easy to accidentally leave a trailing slash if pasting from a URL, but is invalid for Go modules + mod = strings.TrimSuffix(mod, "/") + repl, err = expandPath(repl) + if err != nil { + return err + } + replacements = append(replacements, xk6.NewReplace(mod, repl)) + + case "--output": + if i == len(args)-1 { + return fmt.Errorf("expected value after --output flag") + } + i++ + output = args[i] + outputOverride = true + + default: + if argK6Version != "" { + return fmt.Errorf("missing flag; k6 version already set at %s", argK6Version) + } + argK6Version = args[i] + } } - builder := xk6.FromOSEnv() - if opts.K6Version != "" { - builder.K6Version = opts.K6Version + // prefer k6 version from command line argument over env var + if argK6Version != "" { + k6Version = argK6Version + } + + // ensure an output file is always specified + if output == "" { + output = getK6OutputFile() } - builder.Extensions = opts.Extensions - builder.Replacements = opts.Replacements // perform the build - if err := builder.Build(ctx, log, opts.OutFile); err != nil { - return err + builder := xk6.Builder{ + Compile: xk6.Compile{ + Cgo: os.Getenv("CGO_ENABLED") == "1", + }, + K6Repo: k6Repo, + K6Version: k6Version, + Extensions: extensions, + Replacements: replacements, + RaceDetector: raceDetector, + SkipCleanup: skipCleanup, + } + err := builder.Build(ctx, output) + if err != nil { + log.Fatalf("[FATAL] %v", err) } // prove the build is working by printing the version - output := opts.OutFile if runtime.GOOS == os.Getenv("GOOS") && runtime.GOARCH == os.Getenv("GOARCH") { if !filepath.IsAbs(output) { output = "." + string(filepath.Separator) + output @@ -91,12 +156,13 @@ func runBuild(ctx context.Context, log *slog.Logger, args []string) error { cmd := exec.Command(output, "version") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("executing k6 %v", err) + err = cmd.Run() + if err != nil { + log.Fatalf("[FATAL] %v", err) } } - if !opts.OutputOverride { + if !outputOverride { path, _ := os.Getwd() fmt.Println() fmt.Println("xk6 has now produced a new k6 binary which may be different than the command on your system path!") @@ -106,7 +172,16 @@ func runBuild(ctx context.Context, log *slog.Logger, args []string) error { return nil } -func runDev(ctx context.Context, log *slog.Logger, args []string) error { +func getK6OutputFile() string { + if runtime.GOOS == "windows" { + return ".\\k6.exe" + } + return "./k6" +} + +func runDev(ctx context.Context, args []string) error { + binOutput := getK6OutputFile() + // get current/main module name cmd := exec.Command("go", "list", "-m") cmd.Stderr = os.Stderr @@ -161,24 +236,28 @@ func runDev(ctx context.Context, log *slog.Logger, args []string) error { } importPath := normalizeImportPath(currentModule, cwd, moduleDir) - // create a builder with options from environment variables - builder := xk6.FromOSEnv() - - // set the current module as dependency - builder.Extensions = []xk6.Dependency{ - {PackagePath: importPath}, + // build k6 with this module plugged in + builder := xk6.Builder{ + Compile: xk6.Compile{ + Cgo: os.Getenv("CGO_ENABLED") == "1", + }, + K6Repo: k6Repo, + K6Version: k6Version, + Extensions: []xk6.Dependency{ + {PackagePath: importPath}, + }, + Replacements: replacements, + RaceDetector: raceDetector, + SkipCleanup: skipCleanup, } - // update replacements - builder.Replacements = replacements - - outfile := defaultK6OutputFile() - err = builder.Build(ctx, log, outfile) + err = builder.Build(ctx, binOutput) if err != nil { return err } - log.Info(fmt.Sprintf("Running %v\n\n", append([]string{outfile}, args...))) - cmd = exec.Command(outfile, args...) + log.Printf("[INFO] Running %v\n\n", append([]string{binOutput}, args...)) + + cmd = exec.Command(binOutput, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -186,122 +265,37 @@ func runDev(ctx context.Context, log *slog.Logger, args []string) error { if err != nil { return err } - - return cmd.Wait() -} - -func parseBuildOpts(args []string) (BuildOps, error) { - opts := BuildOps{ - OutFile: defaultK6OutputFile(), - } - - var argK6Version string - - for i := 0; i < len(args); i++ { - switch args[i] { - case "--with": - if i == len(args)-1 { - return BuildOps{}, fmt.Errorf("expected value after --with flag") - } - i++ - mod, ver, repl, err := splitWith(args[i]) - if err != nil { - return BuildOps{}, err - } - mod = strings.TrimSuffix(mod, "/") // easy to accidentally leave a trailing slash if pasting from a URL, but is invalid for Go modules - opts.Extensions = append(opts.Extensions, xk6.Dependency{ - PackagePath: mod, - Version: ver, - }) - if repl != "" { - repl, err = expandPath(repl) - if err != nil { - return BuildOps{}, err - } - opts.Replacements = append(opts.Replacements, xk6.NewReplace(mod, repl)) - } - - case "--replace": - if i == len(args)-1 { - return BuildOps{}, fmt.Errorf("expected value after --replace flag") - } - i++ - mod, _, repl, err := splitWith(args[i]) - if err != nil { - return BuildOps{}, err - } - if repl == "" { - return BuildOps{}, fmt.Errorf("replace value must be of format 'module=replace' or 'module=replace@version'") - } - // easy to accidentally leave a trailing slash if pasting from a URL, but is invalid for Go modules - mod = strings.TrimSuffix(mod, "/") - repl, err = expandPath(repl) - if err != nil { - return BuildOps{}, err - } - opts.Replacements = append(opts.Replacements, xk6.NewReplace(mod, repl)) - - case "--output": - if i == len(args)-1 { - return BuildOps{}, fmt.Errorf("expected value after --output flag") - } - i++ - opts.OutFile = args[i] - opts.OutputOverride = true - - default: - if argK6Version != "" { - return BuildOps{}, fmt.Errorf("missing flag; k6 version already set at %s", argK6Version) - } - argK6Version = args[i] + defer func() { + if skipCleanup { + log.Printf("[INFO] Skipping cleanup as requested; leaving artifact: %s", binOutput) + return } - } - - // prefer k6 version from command line argument over env var - if argK6Version != "" { - opts.K6Version = argK6Version - } + err = os.Remove(binOutput) + if err != nil && !os.IsNotExist(err) { + log.Printf("[ERROR] Deleting temporary binary %s: %v", binOutput, err) + } + }() - return opts, nil + return cmd.Wait() } func normalizeImportPath(currentModule, cwd, moduleDir string) string { return path.Join(currentModule, filepath.ToSlash(strings.TrimPrefix(cwd, moduleDir))) } -func trapSignals(ctx context.Context, log *slog.Logger, cancel context.CancelFunc) { +func trapSignals(ctx context.Context, cancel context.CancelFunc) { sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) select { case <-sig: - log.Info("SIGINT: Shutting down") + log.Printf("[INFO] SIGINT: Shutting down") cancel() case <-ctx.Done(): return } } -func expandPath(path string) (string, error) { - // expand local directory - if path == "." { - if cwd, err := os.Getwd(); err != nil { - return "", err - } else { - return cwd, nil - } - } - // expand ~ as shortcut for home directory - if strings.HasPrefix(path, "~") { - if home, err := os.UserHomeDir(); err != nil { - return "", err - } else { - return strings.Replace(path, "~", home, 1), nil - } - } - return path, nil -} - func splitWith(arg string) (module, version, replace string, err error) { const versionSplit, replaceSplit = "@", "=" @@ -327,9 +321,22 @@ func splitWith(arg string) (module, version, replace string, err error) { return } -func defaultK6OutputFile() string { - if runtime.GOOS == "windows" { - return ".\\k6.exe" +func expandPath(path string) (string, error) { + // expand local directory + if path == "." { + if cwd, err := os.Getwd(); err != nil { + return "", err + } else { + return cwd, nil + } } - return "./k6" + // expand ~ as shortcut for home directory + if strings.HasPrefix(path, "~") { + if home, err := os.UserHomeDir(); err != nil { + return "", err + } else { + return strings.Replace(path, "~", home, 1), nil + } + } + return path, nil } diff --git a/cmd/xk6/main_test.go b/cmd/xk6/main_test.go index f62b0aa..79851b3 100644 --- a/cmd/xk6/main_test.go +++ b/cmd/xk6/main_test.go @@ -1,16 +1,11 @@ package main import ( - "path/filepath" - "reflect" "runtime" "strings" "testing" - - "go.k6.io/xk6" ) - func TestSplitWith(t *testing.T) { t.Parallel() for i, tc := range []struct { @@ -88,43 +83,6 @@ func TestSplitWith(t *testing.T) { } } -func TestExpandPath(t *testing.T) { - t.Run(". expands to current directory", func(t *testing.T) { - t.Parallel() - got, err := expandPath(".") - if got == "." { - t.Errorf("did not expand path") - } - if err != nil { - t.Errorf("failed to expand path") - } - }) - t.Run("~ expands to user's home directory", func(t *testing.T) { - t.Parallel() - got, err := expandPath("~") - if got == "~" { - t.Errorf("did not expand path") - } - if err != nil { - t.Errorf("failed to expand path") - } - switch runtime.GOOS { - case "linux": - if !strings.HasPrefix(got, "/home") { - t.Errorf("did not expand home directory. want=/home/... got=%s", got) - } - case "darwin": - if !strings.HasPrefix(got, "/Users") { - t.Errorf("did not expand home directory. want=/Users/... got=%s", got) - } - case "windows": - if !strings.HasPrefix(got, "C:\\Users") { // could well be another drive letter, but let's assume C:\\ - t.Errorf("did not expand home directory. want=C:\\Users\\... got=%s", got) - } - } - }) -} - func TestNormalizeImportPath(t *testing.T) { t.Parallel() type ( @@ -178,146 +136,39 @@ func TestNormalizeImportPath(t *testing.T) { } } - -func TestParseBuildOpts(t *testing.T) { - testCases := []struct { - title string - args []string - expect BuildOps - expectErr string - }{ - { - title: "parse defaults", - args: []string{}, - expect: BuildOps{ - K6Version: "", - Extensions: nil, - Replacements: nil, - OutFile: defaultK6OutputFile(), - OutputOverride: false, - }, - }, - { - title: "override k6 path", - args: []string{ - "--output", filepath.Join("path", "to", "k6"), - }, - expect: BuildOps{ - K6Version: "", - OutFile: filepath.Join("path", "to", "k6"), - OutputOverride: true, - Extensions: nil, - Replacements: nil, - }, - }, - { - title: "parse k6 version", - args: []string{ - "v0.0.0", - }, - expect: BuildOps{ - K6Version: "v0.0.0", - OutFile: defaultK6OutputFile(), - OutputOverride: false, - Extensions: nil, - Replacements: nil, - }, - }, - { - title: "parse spurious argument", - args: []string{ - "v0.0.0", - "another-arg", - }, - expect: BuildOps{}, - expectErr: "missing flag", - }, - { - title: "parse --with", - args: []string{ - "--with", "github.com/repo/extension@v0.0.0", - }, - expect: BuildOps{ - K6Version: "", - OutFile: defaultK6OutputFile(), - OutputOverride: false, - Extensions: []xk6.Dependency{ - { - PackagePath: "github.com/repo/extension", - Version: "v0.0.0", - }, - }, - Replacements: nil, - }, - }, - { - title: "parse --with with missing value", - args: []string{ - "--with", - }, - expect: BuildOps{}, - expectErr: "expected value after --with flag", - }, - { - title: "parse --with with replacement", - args: []string{ - "--with", "github.com/repo/extension@=github.com/another-repo/extension@v0.0.0", - }, - expect: BuildOps{ - K6Version: "", - OutFile: defaultK6OutputFile(), - OutputOverride: false, - Extensions: []xk6.Dependency{ - { - PackagePath: "github.com/repo/extension", - }, - }, - Replacements: []xk6.Replace{ - { - Old: "github.com/repo/extension", - New: "github.com/another-repo/extension@v0.0.0", - }, - }, - }, - }, - { - title: "parse --replace", - args: []string{ - "--replace", "github.com/repo/extension=github.com/another-repo/extension", - }, - expect: BuildOps{ - K6Version: "", - OutFile: defaultK6OutputFile(), - OutputOverride: false, - Extensions: nil, - Replacements: []xk6.Replace{ - { - Old: "github.com/repo/extension", - New: "github.com/another-repo/extension", - }, - }, - }, - }, - { - title: "parse --replace with missing replace value", - args: []string{ - "--replace", "github.com/repo/extension", - }, - expect: BuildOps{}, - expectErr: "replace value must be of format", - }, - } - - for _, tc := range testCases { - t.Run(tc.title, func(t *testing.T) { - t.Parallel() - got, err := parseBuildOpts(tc.args) - if err != nil && !strings.Contains(err.Error(), tc.expectErr) { - t.Errorf("expected error %v, got %v", tc.expectErr, err) +func TestExpandPath(t *testing.T) { + t.Run(". expands to current directory", func(t *testing.T) { + t.Parallel() + got, err := expandPath(".") + if got == "." { + t.Errorf("did not expand path") + } + if err != nil { + t.Errorf("failed to expand path") + } + }) + t.Run("~ expands to user's home directory", func(t *testing.T) { + t.Parallel() + got, err := expandPath("~") + if got == "~" { + t.Errorf("did not expand path") + } + if err != nil { + t.Errorf("failed to expand path") + } + switch runtime.GOOS { + case "linux": + if !strings.HasPrefix(got, "/home") { + t.Errorf("did not expand home directory. want=/home/... got=%s", got) } - if err == nil && !reflect.DeepEqual(got, tc.expect) { - t.Errorf("expected %v, got %v", tc.expect, got) + case "darwin": + if !strings.HasPrefix(got, "/Users") { + t.Errorf("did not expand home directory. want=/Users/... got=%s", got) } - }) - } -} \ No newline at end of file + case "windows": + if !strings.HasPrefix(got, "C:\\Users") { // could well be another drive letter, but let's assume C:\\ + t.Errorf("did not expand home directory. want=C:\\Users\\... got=%s", got) + } + } + }) +} diff --git a/environment.go b/environment.go new file mode 100644 index 0000000..cd1b270 --- /dev/null +++ b/environment.go @@ -0,0 +1,317 @@ +// Copyright 2020 Matthew Holt +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package xk6 + +import ( + "bytes" + "context" + "fmt" + "html/template" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +func (b Builder) newEnvironment(ctx context.Context) (*environment, error) { + k6ModulePath, err := versionedModulePath(defaultK6ModulePath, b.K6Version) + if err != nil { + return nil, err + } + + // clean up any SIV-incompatible module paths real quick + for i, p := range b.Extensions { + b.Extensions[i].PackagePath, err = versionedModulePath(p.PackagePath, p.Version) + if err != nil { + return nil, err + } + } + + // create the context for the main module template + tplCtx := goModTemplateContext{ + K6Module: k6ModulePath, + } + // evaluate the template for the main module + var buf bytes.Buffer + tpl, err := template.New("main").Parse(mainModuleTemplate) + if err != nil { + return nil, err + } + err = tpl.Execute(&buf, tplCtx) + if err != nil { + return nil, err + } + + // create the folder in which the build environment will operate + tempFolder, err := newTempFolder() + if err != nil { + return nil, err + } + env := &environment{ + k6Version: b.K6Version, + extensions: b.Extensions, + k6ModulePath: k6ModulePath, + tempFolder: tempFolder, + timeoutGoGet: b.TimeoutGet, + skipCleanup: b.SkipCleanup, + } + defer func() { + if err != nil { + err2 := env.Close() + if err2 != nil { + err = fmt.Errorf("%w; additionally, cleaning up folder: %v", err, err2) + } + } + }() + log.Printf("[INFO] Temporary folder: %s", tempFolder) + + // initialize the go module + log.Println("[INFO] Initializing Go module") + cmd := env.newCommand("go", "mod", "init", "k6") + err = env.runCommand(ctx, cmd, 10*time.Second) + if err != nil { + return nil, err + } + + // specify module replacements before pinning versions + replaced := make(map[string]string) + for _, r := range b.Replacements { + log.Printf("[INFO] Replace %s => %s", r.Old.String(), r.New.String()) + err = env.execGoModReplace(ctx, r.Old.Param(), r.New.Param()) + if err != nil { + return nil, err + } + replaced[r.Old.String()] = r.New.String() + } + + // check for early abort + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // pin versions by populating go.mod, first for k6 itself and then extensions + log.Println("[INFO] Pinning versions") + if b.K6Repo != "" { + // building with a forked repo, so get the main one and replace it with + // the fork + err = env.execGoModRequire(ctx, k6ModulePath, "") + if err != nil { + return nil, err + } + replace := b.K6Repo + if b.K6Version != "" { + replace = fmt.Sprintf("%s@%s", b.K6Repo, b.K6Version) + } + err = env.execGoModReplace(ctx, k6ModulePath, replace) + if err != nil { + return nil, err + } + } +nextExt: + for _, p := range b.Extensions { + err = env.writeExtensionImportFile(p.PackagePath) + if err != nil { + return nil, err + } + // if module is locally available, do not "go get" it; + // also note that we iterate and check prefixes, because + // an extension package may be a subfolder of a module, i.e. + // foo/a/extension is within module foo/a. + for repl := range replaced { + if strings.HasPrefix(p.PackagePath, repl) { + continue nextExt + } + } + err = env.execGoModRequire(ctx, p.PackagePath, p.Version) + if err != nil { + return nil, err + } + // check for early abort + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + } + // This is here as we could've not run go mod tidy due to a replace being the only extension + err = env.execGoModTidy(ctx) + if err != nil { + return nil, err + } + + // write the main module file to temporary folder + // we do this last so we get the needed versions from all the replacements and extensions instead of k6 if possible + mainPath := filepath.Join(tempFolder, "main.go") + log.Printf("[INFO] Writing main module: %s", mainPath) + err = os.WriteFile(mainPath, buf.Bytes(), 0o600) + if err != nil { + return nil, err + } + + // building with the default main repo + if b.K6Repo == "" && env.k6Version != "" { + // Only require a specific k6 version if provided. Otherwise extensions + // will require a version they depend on, and Go's module resolution + // algorithm will choose the highest one among all extensions. + err = env.execGoModRequire(ctx, k6ModulePath, env.k6Version) + if err != nil { + return nil, err + } + } + err = env.execGoModTidy(ctx) + if err != nil { + return nil, err + } + + log.Println("[INFO] Build environment ready") + + return env, nil +} + +type environment struct { + k6Version string + extensions []Dependency + k6ModulePath string + tempFolder string + timeoutGoGet time.Duration + skipCleanup bool +} + +// Close cleans up the build environment, including deleting +// the temporary folder from the disk. +func (env environment) Close() error { + if env.skipCleanup { + log.Printf("[INFO] Skipping cleanup as requested; leaving folder intact: %s", env.tempFolder) + return nil + } + log.Printf("[INFO] Cleaning up temporary folder: %s", env.tempFolder) + return os.RemoveAll(env.tempFolder) +} + +func (env environment) writeExtensionImportFile(packagePath string) error { + fileContents := fmt.Sprintf(`package main +import _ %q +`, packagePath) + filePath := filepath.Join(env.tempFolder, strings.ReplaceAll(packagePath, "/", "_")+".go") + return os.WriteFile(filePath, []byte(fileContents), 0o600) +} + +func (env environment) newCommand(command string, args ...string) *exec.Cmd { + cmd := exec.Command(command, args...) + cmd.Dir = env.tempFolder + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd +} + +func (env environment) runCommand(ctx context.Context, cmd *exec.Cmd, timeout time.Duration) error { + log.Printf("[INFO] exec (timeout=%s): %+v ", timeout, cmd) + + if timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + + // start the command; if it fails to start, report error immediately + err := cmd.Start() + if err != nil { + return err + } + + // wait for the command in a goroutine; the reason for this is + // very subtle: if, in our select, we do `case cmdErr := <-cmd.Wait()`, + // then that case would be chosen immediately, because cmd.Wait() is + // immediately available (even though it blocks for potentially a long + // time, it can be evaluated immediately). So we have to remove that + // evaluation from the `case` statement. + cmdErrChan := make(chan error) + go func() { + cmdErrChan <- cmd.Wait() + }() + + // unblock either when the command finishes, or when the done + // channel is closed -- whichever comes first + select { + case cmdErr := <-cmdErrChan: + // process ended; report any error immediately + return cmdErr + case <-ctx.Done(): + // context was canceled, either due to timeout or + // maybe a signal from higher up canceled the parent + // context; presumably, the OS also sent the signal + // to the child process, so wait for it to die + select { + case <-time.After(15 * time.Second): + _ = cmd.Process.Kill() + case <-cmdErrChan: + } + return ctx.Err() + } +} + +// tidy the module to ensure go.mod will not have versions such as `latest` +func (env environment) execGoModTidy(ctx context.Context) error { + tidyCmd := env.newCommand("go", "mod", "tidy", "-compat=1.17") + return env.runCommand(ctx, tidyCmd, env.timeoutGoGet) +} + +func (env environment) execGoModRequire(ctx context.Context, modulePath, moduleVersion string) error { + mod := modulePath + if moduleVersion != "" { + mod += "@" + moduleVersion + } else { + mod += "@latest" + } + cmd := env.newCommand("go", "mod", "edit", "-require", mod) + err := env.runCommand(ctx, cmd, env.timeoutGoGet) + if err != nil { + return err + } + return env.execGoModTidy(ctx) +} + +func (env environment) execGoModReplace(ctx context.Context, modulePath, replaceRepo string) error { + replace := fmt.Sprintf("%s=%s", modulePath, replaceRepo) + cmd := env.newCommand("go", "mod", "edit", "-replace", replace) + err := env.runCommand(ctx, cmd, env.timeoutGoGet) + if err != nil { + return err + } + return env.execGoModTidy(ctx) +} + +type goModTemplateContext struct { + K6Module string +} + +const mainModuleTemplate = `package main + +import ( + k6cmd "{{.K6Module}}/cmd" + + // plug in k6 modules here + // TODO: Create /modules/standard dir structure? + // _ "{{.K6Module}}/modules/standard" +) + +func main() { + k6cmd.Execute() +} +` diff --git a/go.mod b/go.mod index 1f187e8..bffff5b 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,5 @@ module go.k6.io/xk6 -go 1.22.2 +go 1.16 -toolchain go1.22.4 - -require github.com/grafana/k6foundry v0.3.0 - -require golang.org/x/mod v0.21.0 // indirect +require github.com/Masterminds/semver/v3 v3.1.1 diff --git a/go.sum b/go.sum index c351bdd..471bde9 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ -github.com/grafana/k6foundry v0.3.0 h1:C+6dPbsOv7Uq4hEhBFNuYqmTdE9jQ0VqhXqBDtMkVTE= -github.com/grafana/k6foundry v0.3.0/go.mod h1:/NtBSQQgXup5SVbfInl0Q8zKVx08xgvXIZ0xncqexEs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= diff --git a/vendor/github.com/Masterminds/semver/v3/.gitignore b/vendor/github.com/Masterminds/semver/v3/.gitignore new file mode 100644 index 0000000..6b061e6 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/.gitignore @@ -0,0 +1 @@ +_fuzz/ \ No newline at end of file diff --git a/vendor/github.com/Masterminds/semver/v3/.golangci.yml b/vendor/github.com/Masterminds/semver/v3/.golangci.yml new file mode 100644 index 0000000..fdbdf14 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/.golangci.yml @@ -0,0 +1,26 @@ +run: + deadline: 2m + +linters: + disable-all: true + enable: + - deadcode + - dupl + - errcheck + - gofmt + - goimports + - golint + - gosimple + - govet + - ineffassign + - misspell + - nakedret + - structcheck + - unused + - varcheck + +linters-settings: + gofmt: + simplify: true + dupl: + threshold: 400 diff --git a/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md b/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md new file mode 100644 index 0000000..1f90c38 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md @@ -0,0 +1,194 @@ +# Changelog + +## 3.1.1 (2020-11-23) + +### Fixed + +- #158: Fixed issue with generated regex operation order that could cause problem + +## 3.1.0 (2020-04-15) + +### Added + +- #131: Add support for serializing/deserializing SQL (thanks @ryancurrah) + +### Changed + +- #148: More accurate validation messages on constraints + +## 3.0.3 (2019-12-13) + +### Fixed + +- #141: Fixed issue with <= comparison + +## 3.0.2 (2019-11-14) + +### Fixed + +- #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos) + +## 3.0.1 (2019-09-13) + +### Fixed + +- #125: Fixes issue with module path for v3 + +## 3.0.0 (2019-09-12) + +This is a major release of the semver package which includes API changes. The Go +API is compatible with ^1. The Go API was not changed because many people are using +`go get` without Go modules for their applications and API breaking changes cause +errors which we have or would need to support. + +The changes in this release are the handling based on the data passed into the +functions. These are described in the added and changed sections below. + +### Added + +- StrictNewVersion function. This is similar to NewVersion but will return an + error if the version passed in is not a strict semantic version. For example, + 1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly + speaking semantic versions. This function is faster, performs fewer operations, + and uses fewer allocations than NewVersion. +- Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint. + The Makefile contains the operations used. For more information on you can start + on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing +- Now using Go modules + +### Changed + +- NewVersion has proper prerelease and metadata validation with error messages + to signal an issue with either of them +- ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the + version is >=1 the ^ ranges works the same as v1. For major versions of 0 the + rules have changed. The minor version is treated as the stable version unless + a patch is specified and then it is equivalent to =. One difference from npm/js + is that prereleases there are only to a specific version (e.g. 1.2.3). + Prereleases here look over multiple versions and follow semantic version + ordering rules. This pattern now follows along with the expected and requested + handling of this packaged by numerous users. + +## 1.5.0 (2019-09-11) + +### Added + +- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) + +### Changed + +- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) +- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) +- #72: Adding docs comment pointing to vert for a cli +- #71: Update the docs on pre-release comparator handling +- #89: Test with new go versions (thanks @thedevsaddam) +- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) + +### Fixed + +- #78: Fix unchecked error in example code (thanks @ravron) +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case +- #97: Fixed copyright file for proper display on GitHub +- #107: Fix handling prerelease when sorting alphanum and num +- #109: Fixed where Validate sometimes returns wrong message on error + +## 1.4.2 (2018-04-10) + +### Changed + +- #72: Updated the docs to point to vert for a console appliaction +- #71: Update the docs on pre-release comparator handling + +### Fixed + +- #70: Fix the handling of pre-releases and the 0.0.0 release edge case + +## 1.4.1 (2018-04-02) + +### Fixed + +- Fixed #64: Fix pre-release precedence issue (thanks @uudashr) + +## 1.4.0 (2017-10-04) + +### Changed + +- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) + +## 1.3.1 (2017-07-10) + +### Fixed + +- Fixed #57: number comparisons in prerelease sometimes inaccurate + +## 1.3.0 (2017-05-02) + +### Added + +- #45: Added json (un)marshaling support (thanks @mh-cbon) +- Stability marker. See https://masterminds.github.io/stability/ + +### Fixed + +- #51: Fix handling of single digit tilde constraint (thanks @dgodd) + +### Changed + +- #55: The godoc icon moved from png to svg + +## 1.2.3 (2017-04-03) + +### Fixed + +- #46: Fixed 0.x.x and 0.0.x in constraints being treated as * + +## Release 1.2.2 (2016-12-13) + +### Fixed + +- #34: Fixed issue where hyphen range was not working with pre-release parsing. + +## Release 1.2.1 (2016-11-28) + +### Fixed + +- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" + properly. + +## Release 1.2.0 (2016-11-04) + +### Added + +- #20: Added MustParse function for versions (thanks @adamreese) +- #15: Added increment methods on versions (thanks @mh-cbon) + +### Fixed + +- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and + might not satisfy the intended compatibility. The change here ignores pre-releases + on constraint checks (e.g., ~ or ^) when a pre-release is not part of the + constraint. For example, `^1.2.3` will ignore pre-releases while + `^1.2.3-alpha` will include them. + +## Release 1.1.1 (2016-06-30) + +### Changed + +- Issue #9: Speed up version comparison performance (thanks @sdboyer) +- Issue #8: Added benchmarks (thanks @sdboyer) +- Updated Go Report Card URL to new location +- Updated Readme to add code snippet formatting (thanks @mh-cbon) +- Updating tagging to v[SemVer] structure for compatibility with other tools. + +## Release 1.1.0 (2016-03-11) + +- Issue #2: Implemented validation to provide reasons a versions failed a + constraint. + +## Release 1.0.1 (2015-12-31) + +- Fixed #1: * constraint failing on valid versions. + +## Release 1.0.0 (2015-10-20) + +- Initial release diff --git a/vendor/github.com/Masterminds/semver/v3/LICENSE.txt b/vendor/github.com/Masterminds/semver/v3/LICENSE.txt new file mode 100644 index 0000000..9ff7da9 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2014-2019, Matt Butcher and Matt Farina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Masterminds/semver/v3/Makefile b/vendor/github.com/Masterminds/semver/v3/Makefile new file mode 100644 index 0000000..eac1917 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/Makefile @@ -0,0 +1,37 @@ +GOPATH=$(shell go env GOPATH) +GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint +GOFUZZBUILD = $(GOPATH)/bin/go-fuzz-build +GOFUZZ = $(GOPATH)/bin/go-fuzz + +.PHONY: lint +lint: $(GOLANGCI_LINT) + @echo "==> Linting codebase" + @$(GOLANGCI_LINT) run + +.PHONY: test +test: + @echo "==> Running tests" + GO111MODULE=on go test -v + +.PHONY: test-cover +test-cover: + @echo "==> Running Tests with coverage" + GO111MODULE=on go test -cover . + +.PHONY: fuzz +fuzz: $(GOFUZZBUILD) $(GOFUZZ) + @echo "==> Fuzz testing" + $(GOFUZZBUILD) + $(GOFUZZ) -workdir=_fuzz + +$(GOLANGCI_LINT): + # Install golangci-lint. The configuration for it is in the .golangci.yml + # file in the root of the repository + echo ${GOPATH} + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1 + +$(GOFUZZBUILD): + cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz-build + +$(GOFUZZ): + cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-dep \ No newline at end of file diff --git a/vendor/github.com/Masterminds/semver/v3/README.md b/vendor/github.com/Masterminds/semver/v3/README.md new file mode 100644 index 0000000..d8f54dc --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/README.md @@ -0,0 +1,244 @@ +# SemVer + +The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: + +* Parse semantic versions +* Sort semantic versions +* Check if a semantic version fits within a set of constraints +* Optionally work with a `v` prefix + +[![Stability: +Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) +[![](https://github.com/Masterminds/semver/workflows/Tests/badge.svg)](https://github.com/Masterminds/semver/actions) +[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3) +[![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) + +If you are looking for a command line tool for version comparisons please see +[vert](https://github.com/Masterminds/vert) which uses this library. + +## Package Versions + +There are three major versions fo the `semver` package. + +* 3.x.x is the new stable and active version. This version is focused on constraint + compatibility for range handling in other tools from other languages. It has + a similar API to the v1 releases. The development of this version is on the master + branch. The documentation for this version is below. +* 2.x was developed primarily for [dep](https://github.com/golang/dep). There are + no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer). + There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x). +* 1.x.x is the most widely used version with numerous tagged releases. This is the + previous stable and is still maintained for bug fixes. The development, to fix + bugs, occurs on the release-1 branch. You can read the documentation [here](https://github.com/Masterminds/semver/blob/release-1/README.md). + +## Parsing Semantic Versions + +There are two functions that can parse semantic versions. The `StrictNewVersion` +function only parses valid version 2 semantic versions as outlined in the +specification. The `NewVersion` function attempts to coerce a version into a +semantic version and parse it. For example, if there is a leading v or a version +listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid +semantic version (e.g., 1.2.0). In both cases a `Version` object is returned +that can be sorted, compared, and used in constraints. + +When parsing a version an error is returned if there is an issue parsing the +version. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+build345") + +The version object has methods to get the parts of the version, compare it to +other versions, convert the version back into a string, and get the original +string. Getting the original string is useful if the semantic version was coerced +into a valid form. + +## Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + +```go +raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} +vs := make([]*semver.Version, len(raw)) +for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v +} + +sort.Sort(semver.Collection(vs)) +``` + +## Checking Version Constraints + +There are two methods for comparing versions. One uses comparison methods on +`Version` instances and the other uses `Constraints`. There are some important +differences to notes between these two methods of comparison. + +1. When two versions are compared using functions such as `Compare`, `LessThan`, + and others it will follow the specification and always include prereleases + within the comparison. It will provide an answer that is valid with the + comparison section of the spec at https://semver.org/#spec-item-11 +2. When constraint checking is used for checks or validation it will follow a + different set of rules that are common for ranges with tools like npm/js + and Rust/Cargo. This includes considering prereleases to be invalid if the + ranges does not include one. If you want to have it include pre-releases a + simple solution is to include `-0` in your range. +3. Constraint ranges can have some complex rules including the shorthand use of + ~ and ^. For more details on those see the options below. + +There are differences between the two methods or checking versions because the +comparison methods on `Version` follow the specification while comparison ranges +are not part of the specification. Different packages and tools have taken it +upon themselves to come up with range rules. This has resulted in differences. +For example, npm/js and Cargo/Rust follow similar patterns while PHP has a +different pattern for ^. The comparison features in this package follow the +npm/js and Cargo/Rust lead because applications using it have followed similar +patters with their versions. + +Checking a version against version constraints is one of the most featureful +parts of the package. + +```go +c, err := semver.NewConstraint(">= 1.2.3") +if err != nil { + // Handle constraint not being parsable. +} + +v, err := semver.NewVersion("1.3") +if err != nil { + // Handle version not being parsable. +} +// Check if the version meets the constraints. The a variable will be true. +a := c.Check(v) +``` + +### Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of space or comma separated AND comparisons. These are then separated by || (OR) +comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + +* `=`: equal (aliased to no operator) +* `!=`: not equal +* `>`: greater than +* `<`: less than +* `>=`: greater than or equal to +* `<=`: less than or equal to + +### Working With Prerelease Versions + +Pre-releases, for those not familiar with them, are used for software releases +prior to stable or generally available releases. Examples of prereleases include +development, alpha, beta, and release candidate releases. A prerelease may be +a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the +order of precedence, prereleases come before their associated releases. In this +example `1.2.3-beta.1 < 1.2.3`. + +According to the Semantic Version specification prereleases may not be +API compliant with their release counterpart. It says, + +> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. + +SemVer comparisons using constraints without a prerelease comparator will skip +prerelease versions. For example, `>=1.2.3` will skip prereleases when looking +at a list of releases while `>=1.2.3-0` will evaluate and find prereleases. + +The reason for the `0` as a pre-release version in the example comparison is +because pre-releases can only contain ASCII alphanumerics and hyphens (along with +`.` separators), per the spec. Sorting happens in ASCII sort order, again per the +spec. The lowest character is a `0` in ASCII sort order +(see an [ASCII Table](http://www.asciitable.com/)) + +Understanding ASCII sort ordering is important because A-Z comes before a-z. That +means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case +sensitivity doesn't apply here. This is due to ASCII sort ordering which is what +the spec specifies. + +### Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + +* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5` +* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` + +### Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the patch level comparison (see tilde below). For example, + +* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `>= 1.2.x` is equivalent to `>= 1.2.0` +* `<= 2.x` is equivalent to `< 3` +* `*` is equivalent to `>= 0.0.0` + +### Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + +* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` +* `~1` is equivalent to `>= 1, < 2` +* `~2.3` is equivalent to `>= 2.3, < 2.4` +* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `~1.x` is equivalent to `>= 1, < 2` + +### Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes once a stable +(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts +as the API stability level. This is useful when comparisons of API versions as a +major change is API breaking. For example, + +* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` +* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` +* `^2.3` is equivalent to `>= 2.3, < 3` +* `^2.x` is equivalent to `>= 2.0.0, < 3` +* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` +* `^0.2` is equivalent to `>=0.2.0 <0.3.0` +* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` +* `^0.0` is equivalent to `>=0.0.0 <0.1.0` +* `^0` is equivalent to `>=0.0.0 <1.0.0` + +## Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + +```go +c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") +if err != nil { + // Handle constraint not being parseable. +} + +v, err := semver.NewVersion("1.3") +if err != nil { + // Handle version not being parseable. +} + +// Validate a version against a constraint. +a, msgs := c.Validate(v) +// a is false +for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" +} +``` + +## Contribute + +If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) +or [create a pull request](https://github.com/Masterminds/semver/pulls). diff --git a/vendor/github.com/Masterminds/semver/v3/collection.go b/vendor/github.com/Masterminds/semver/v3/collection.go new file mode 100644 index 0000000..a782358 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/collection.go @@ -0,0 +1,24 @@ +package semver + +// Collection is a collection of Version instances and implements the sort +// interface. See the sort package for more details. +// https://golang.org/pkg/sort/ +type Collection []*Version + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c Collection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c Collection) Less(i, j int) bool { + return c[i].LessThan(c[j]) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c Collection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/vendor/github.com/Masterminds/semver/v3/constraints.go b/vendor/github.com/Masterminds/semver/v3/constraints.go new file mode 100644 index 0000000..547613f --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/constraints.go @@ -0,0 +1,568 @@ +package semver + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" +) + +// Constraints is one or more constraint that a semantic version can be +// checked against. +type Constraints struct { + constraints [][]*constraint +} + +// NewConstraint returns a Constraints instance that a Version instance can +// be checked against. If there is a parse error it will be returned. +func NewConstraint(c string) (*Constraints, error) { + + // Rewrite - ranges into a comparison operation. + c = rewriteRange(c) + + ors := strings.Split(c, "||") + or := make([][]*constraint, len(ors)) + for k, v := range ors { + + // TODO: Find a way to validate and fetch all the constraints in a simpler form + + // Validate the segment + if !validConstraintRegex.MatchString(v) { + return nil, fmt.Errorf("improper constraint: %s", v) + } + + cs := findConstraintRegex.FindAllString(v, -1) + if cs == nil { + cs = append(cs, v) + } + result := make([]*constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s) + if err != nil { + return nil, err + } + + result[i] = pc + } + or[k] = result + } + + o := &Constraints{constraints: or} + return o, nil +} + +// Check tests if a version satisfies the constraints. +func (cs Constraints) Check(v *Version) bool { + // TODO(mattfarina): For v4 of this library consolidate the Check and Validate + // functions as the underlying functions make that possible now. + // loop over the ORs and check the inner ANDs + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if check, _ := c.check(v); !check { + joy = false + break + } + } + + if joy { + return true + } + } + + return false +} + +// Validate checks if a version satisfies a constraint. If not a slice of +// reasons for the failure are returned in addition to a bool. +func (cs Constraints) Validate(v *Version) (bool, []error) { + // loop over the ORs and check the inner ANDs + var e []error + + // Capture the prerelease message only once. When it happens the first time + // this var is marked + var prerelesase bool + for _, o := range cs.constraints { + joy := true + for _, c := range o { + // Before running the check handle the case there the version is + // a prerelease and the check is not searching for prereleases. + if c.con.pre == "" && v.pre != "" { + if !prerelesase { + em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + e = append(e, em) + prerelesase = true + } + joy = false + + } else { + + if _, err := c.check(v); err != nil { + e = append(e, err) + joy = false + } + } + } + + if joy { + return true, []error{} + } + } + + return false, e +} + +func (cs Constraints) String() string { + buf := make([]string, len(cs.constraints)) + var tmp bytes.Buffer + + for k, v := range cs.constraints { + tmp.Reset() + vlen := len(v) + for kk, c := range v { + tmp.WriteString(c.string()) + + // Space separate the AND conditions + if vlen > 1 && kk < vlen-1 { + tmp.WriteString(" ") + } + } + buf[k] = tmp.String() + } + + return strings.Join(buf, " || ") +} + +var constraintOps map[string]cfunc +var constraintRegex *regexp.Regexp +var constraintRangeRegex *regexp.Regexp + +// Used to find individual constraints within a multi-constraint string +var findConstraintRegex *regexp.Regexp + +// Used to validate an segment of ANDs is valid +var validConstraintRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +func init() { + constraintOps = map[string]cfunc{ + "": constraintTildeOrEqual, + "=": constraintTildeOrEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~": constraintTilde, + "~>": constraintTilde, + "^": constraintCaret, + } + + ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^` + + constraintRegex = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + ops, + cvRegex)) + + constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( + `\s*(%s)\s+-\s+(%s)\s*`, + cvRegex, cvRegex)) + + findConstraintRegex = regexp.MustCompile(fmt.Sprintf( + `(%s)\s*(%s)`, + ops, + cvRegex)) + + validConstraintRegex = regexp.MustCompile(fmt.Sprintf( + `^(\s*(%s)\s*(%s)\s*\,?)+$`, + ops, + cvRegex)) +} + +// An individual constraint +type constraint struct { + // The version used in the constraint check. For example, if a constraint + // is '<= 2.0.0' the con a version instance representing 2.0.0. + con *Version + + // The original parsed version (e.g., 4.x from != 4.x) + orig string + + // The original operator for the constraint + origfunc string + + // When an x is used as part of the version (e.g., 1.x) + minorDirty bool + dirty bool + patchDirty bool +} + +// Check if a version meets the constraint +func (c *constraint) check(v *Version) (bool, error) { + return constraintOps[c.origfunc](v, c) +} + +// String prints an individual constraint into a string +func (c *constraint) string() string { + return c.origfunc + c.orig +} + +type cfunc func(v *Version, c *constraint) (bool, error) + +func parseConstraint(c string) (*constraint, error) { + if len(c) > 0 { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + cs := &constraint{ + orig: m[2], + origfunc: m[1], + } + + ver := m[2] + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) || m[3] == "" { + ver = "0.0.0" + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs.con = con + cs.minorDirty = minorDirty + cs.patchDirty = patchDirty + cs.dirty = dirty + + return cs, nil + } + + // The rest is the special case where an empty string was passed in which + // is equivalent to * or >=0.0.0 + con, err := StrictNewVersion("0.0.0") + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs := &constraint{ + con: con, + orig: c, + origfunc: "", + minorDirty: false, + patchDirty: false, + dirty: true, + } + return cs, nil +} + +// Constraint functions +func constraintNotEqual(v *Version, c *constraint) (bool, error) { + if c.dirty { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if c.con.Major() != v.Major() { + return true, nil + } + if c.con.Minor() != v.Minor() && !c.minorDirty { + return true, nil + } else if c.minorDirty { + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } else if c.con.Patch() != v.Patch() && !c.patchDirty { + return true, nil + } else if c.patchDirty { + // Need to handle prereleases if present + if v.Prerelease() != "" || c.con.Prerelease() != "" { + eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + } + + eq := v.Equal(c.con) + if eq { + return false, fmt.Errorf("%s is equal to %s", v, c.orig) + } + + return true, nil +} + +func constraintGreaterThan(v *Version, c *constraint) (bool, error) { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + var eq bool + + if !c.dirty { + eq = v.Compare(c.con) == 1 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } + + if v.Major() > c.con.Major() { + return true, nil + } else if v.Major() < c.con.Major() { + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } else if c.minorDirty { + // This is a range case such as >11. When the version is something like + // 11.1.0 is it not > 11. For that we would need 12 or higher + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } else if c.patchDirty { + // This is for ranges such as >11.1. A version of 11.1.1 is not greater + // which one of 11.2.1 is greater + eq = v.Minor() > c.con.Minor() + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) + } + + // If we have gotten here we are not comparing pre-preleases and can use the + // Compare function to accomplish that. + eq = v.Compare(c.con) == 1 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig) +} + +func constraintLessThan(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + eq := v.Compare(c.con) < 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig) +} + +func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + eq := v.Compare(c.con) >= 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is less than %s", v, c.orig) +} + +func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + var eq bool + + if !c.dirty { + eq = v.Compare(c.con) <= 0 + if eq { + return true, nil + } + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } + + if v.Major() > c.con.Major() { + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty { + return false, fmt.Errorf("%s is greater than %s", v, c.orig) + } + + return true, nil +} + +// ~*, ~>* --> >= 0.0.0 (any) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 +func constraintTilde(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if v.LessThan(c.con) { + return false, fmt.Errorf("%s is less than %s", v, c.orig) + } + + // ~0.0.0 is a special case where all constraints are accepted. It's + // equivalent to >= 0.0.0. + if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && + !c.minorDirty && !c.patchDirty { + return true, nil + } + + if v.Major() != c.con.Major() { + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + + if v.Minor() != c.con.Minor() && !c.minorDirty { + return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig) + } + + return true, nil +} + +// When there is a .x (dirty) status it automatically opts in to ~. Otherwise +// it's a straight = +func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + if c.dirty { + return constraintTilde(v, c) + } + + eq := v.Equal(c.con) + if eq { + return true, nil + } + + return false, fmt.Errorf("%s is not equal to %s", v, c.orig) +} + +// ^* --> (any) +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2 --> >=1.2.0 <2.0.0 +// ^1 --> >=1.0.0 <2.0.0 +// ^0.2.3 --> >=0.2.3 <0.3.0 +// ^0.2 --> >=0.2.0 <0.3.0 +// ^0.0.3 --> >=0.0.3 <0.0.4 +// ^0.0 --> >=0.0.0 <0.1.0 +// ^0 --> >=0.0.0 <1.0.0 +func constraintCaret(v *Version, c *constraint) (bool, error) { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) + } + + // This less than handles prereleases + if v.LessThan(c.con) { + return false, fmt.Errorf("%s is less than %s", v, c.orig) + } + + var eq bool + + // ^ when the major > 0 is >=x.y.z < x+1 + if c.con.Major() > 0 || c.minorDirty { + + // ^ has to be within a major range for > 0. Everything less than was + // filtered out with the LessThan call above. This filters out those + // that greater but not within the same major range. + eq = v.Major() == c.con.Major() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + + // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 + if c.con.Major() == 0 && v.Major() > 0 { + return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig) + } + // If the con Minor is > 0 it is not dirty + if c.con.Minor() > 0 || c.patchDirty { + eq = v.Minor() == c.con.Minor() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig) + } + + // At this point the major is 0 and the minor is 0 and not dirty. The patch + // is not dirty so we need to check if they are equal. If they are not equal + eq = c.con.Patch() == v.Patch() + if eq { + return true, nil + } + return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig) +} + +func isX(x string) bool { + switch x { + case "x", "*", "X": + return true + default: + return false + } +} + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} diff --git a/vendor/github.com/Masterminds/semver/v3/doc.go b/vendor/github.com/Masterminds/semver/v3/doc.go new file mode 100644 index 0000000..391aa46 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/doc.go @@ -0,0 +1,184 @@ +/* +Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. + +Specifically it provides the ability to: + + * Parse semantic versions + * Sort semantic versions + * Check if a semantic version fits within a set of constraints + * Optionally work with a `v` prefix + +Parsing Semantic Versions + +There are two functions that can parse semantic versions. The `StrictNewVersion` +function only parses valid version 2 semantic versions as outlined in the +specification. The `NewVersion` function attempts to coerce a version into a +semantic version and parse it. For example, if there is a leading v or a version +listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid +semantic version (e.g., 1.2.0). In both cases a `Version` object is returned +that can be sorted, compared, and used in constraints. + +When parsing a version an optional error can be returned if there is an issue +parsing the version. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+b345") + +The version object has methods to get the parts of the version, compare it to +other versions, convert the version back into a string, and get the original +string. For more details please see the documentation +at https://godoc.org/github.com/Masterminds/semver. + +Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) + +Checking Version Constraints and Comparing Versions + +There are two methods for comparing versions. One uses comparison methods on +`Version` instances and the other is using Constraints. There are some important +differences to notes between these two methods of comparison. + +1. When two versions are compared using functions such as `Compare`, `LessThan`, + and others it will follow the specification and always include prereleases + within the comparison. It will provide an answer valid with the comparison + spec section at https://semver.org/#spec-item-11 +2. When constraint checking is used for checks or validation it will follow a + different set of rules that are common for ranges with tools like npm/js + and Rust/Cargo. This includes considering prereleases to be invalid if the + ranges does not include on. If you want to have it include pre-releases a + simple solution is to include `-0` in your range. +3. Constraint ranges can have some complex rules including the shorthard use of + ~ and ^. For more details on those see the options below. + +There are differences between the two methods or checking versions because the +comparison methods on `Version` follow the specification while comparison ranges +are not part of the specification. Different packages and tools have taken it +upon themselves to come up with range rules. This has resulted in differences. +For example, npm/js and Cargo/Rust follow similar patterns which PHP has a +different pattern for ^. The comparison features in this package follow the +npm/js and Cargo/Rust lead because applications using it have followed similar +patters with their versions. + +Checking a version against version constraints is one of the most featureful +parts of the package. + + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parsable. + } + + v, err := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parsable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) + +Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma or space separated AND comparisons. These are then separated by || (OR) +comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. This can also be written as +`">= 1.2, < 3.0.0 || >= 4.2.3"` + +The basic comparisons are: + + * `=`: equal (aliased to no operator) + * `!=`: not equal + * `>`: greater than + * `<`: less than + * `>=`: greater than or equal to + * `<=`: less than or equal to + +Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + + * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` + * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` + +Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the tilde operation. For example, + + * `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` + * `>= 1.2.x` is equivalent to `>= 1.2.0` + * `<= 2.x` is equivalent to `<= 3` + * `*` is equivalent to `>= 0.0.0` + +Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + + * `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0` + * `~1` is equivalent to `>= 1, < 2` + * `~2.3` is equivalent to `>= 2.3 < 2.4` + * `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` + * `~1.x` is equivalent to `>= 1 < 2` + +Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes once a stable +(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts +as the API stability level. This is useful when comparisons of API versions as a +major change is API breaking. For example, + + * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` + * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` + * `^2.3` is equivalent to `>= 2.3, < 3` + * `^2.x` is equivalent to `>= 2.0.0, < 3` + * `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` + * `^0.2` is equivalent to `>=0.2.0 <0.3.0` + * `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` + * `^0.0` is equivalent to `>=0.0.0 <0.1.0` + * `^0` is equivalent to `>=0.0.0 <1.0.0` + +Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + + c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + + // Validate a version against a constraint. + a, msgs := c.Validate(v) + // a is false + for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" + } +*/ +package semver diff --git a/vendor/github.com/Masterminds/semver/v3/fuzz.go b/vendor/github.com/Masterminds/semver/v3/fuzz.go new file mode 100644 index 0000000..a242ad7 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/fuzz.go @@ -0,0 +1,22 @@ +// +build gofuzz + +package semver + +func Fuzz(data []byte) int { + d := string(data) + + // Test NewVersion + _, _ = NewVersion(d) + + // Test StrictNewVersion + _, _ = StrictNewVersion(d) + + // Test NewConstraint + _, _ = NewConstraint(d) + + // The return value should be 0 normally, 1 if the priority in future tests + // should be increased, and -1 if future tests should skip passing in that + // data. We do not have a reason to change priority so 0 is always returned. + // There are example tests that do this. + return 0 +} diff --git a/vendor/github.com/Masterminds/semver/v3/go.mod b/vendor/github.com/Masterminds/semver/v3/go.mod new file mode 100644 index 0000000..658233c --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/go.mod @@ -0,0 +1,3 @@ +module github.com/Masterminds/semver/v3 + +go 1.12 diff --git a/vendor/github.com/Masterminds/semver/v3/version.go b/vendor/github.com/Masterminds/semver/v3/version.go new file mode 100644 index 0000000..d6b9cda --- /dev/null +++ b/vendor/github.com/Masterminds/semver/v3/version.go @@ -0,0 +1,606 @@ +package semver + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +// The compiled version of the regex created at init() is cached here so it +// only needs to be created once. +var versionRegex *regexp.Regexp + +var ( + // ErrInvalidSemVer is returned a version is found to be invalid when + // being parsed. + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + + // ErrEmptyString is returned when an empty string is passed in for parsing. + ErrEmptyString = errors.New("Version string empty") + + // ErrInvalidCharacters is returned when invalid characters are found as + // part of a version + ErrInvalidCharacters = errors.New("Invalid characters in version") + + // ErrSegmentStartsZero is returned when a version segment starts with 0. + // This is invalid in SemVer. + ErrSegmentStartsZero = errors.New("Version segment starts with 0") + + // ErrInvalidMetadata is returned when the metadata is an invalid format + ErrInvalidMetadata = errors.New("Invalid Metadata string") + + // ErrInvalidPrerelease is returned when the pre-release is an invalid format + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") +) + +// semVerRegex is the regular expression used to parse a semantic version. +const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +// Version represents a single semantic version. +type Version struct { + major, minor, patch uint64 + pre string + metadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + semVerRegex + "$") +} + +const num string = "0123456789" +const allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num + +// StrictNewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. Only parses valid semantic versions. +// Performs checking that can find errors within the version. +// If you want to coerce a version, such as 1 or 1.2, and perse that as the 1.x +// releases of semver provided use the NewSemver() function. +func StrictNewVersion(v string) (*Version, error) { + // Parsing here does not use RegEx in order to increase performance and reduce + // allocations. + + if len(v) == 0 { + return nil, ErrEmptyString + } + + // Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build + parts := strings.SplitN(v, ".", 3) + if len(parts) != 3 { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + original: v, + } + + // check for prerelease or build metadata + var extra []string + if strings.ContainsAny(parts[2], "-+") { + // Start with the build metadata first as it needs to be on the right + extra = strings.SplitN(parts[2], "+", 2) + if len(extra) > 1 { + // build metadata found + sv.metadata = extra[1] + parts[2] = extra[0] + } + + extra = strings.SplitN(parts[2], "-", 2) + if len(extra) > 1 { + // prerelease found + sv.pre = extra[1] + parts[2] = extra[0] + } + } + + // Validate the number segments are valid. This includes only having positive + // numbers and no leading 0's. + for _, p := range parts { + if !containsOnly(p, num) { + return nil, ErrInvalidCharacters + } + + if len(p) > 1 && p[0] == '0' { + return nil, ErrSegmentStartsZero + } + } + + // Extract the major, minor, and patch elements onto the returned Version + var err error + sv.major, err = strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return nil, err + } + + sv.minor, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return nil, err + } + + sv.patch, err = strconv.ParseUint(parts[2], 10, 64) + if err != nil { + return nil, err + } + + // No prerelease or build metadata found so returning now as a fastpath. + if sv.pre == "" && sv.metadata == "" { + return sv, nil + } + + if sv.pre != "" { + if err = validatePrerelease(sv.pre); err != nil { + return nil, err + } + } + + if sv.metadata != "" { + if err = validateMetadata(sv.metadata); err != nil { + return nil, err + } + } + + return sv, nil +} + +// NewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. If the version is SemVer-ish it +// attempts to convert it to SemVer. If you want to validate it was a strict +// semantic version at parse time see StrictNewVersion(). +func NewVersion(v string) (*Version, error) { + m := versionRegex.FindStringSubmatch(v) + if m == nil { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + metadata: m[8], + pre: m[5], + original: v, + } + + var err error + sv.major, err = strconv.ParseUint(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + + if m[2] != "" { + sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + } else { + sv.minor = 0 + } + + if m[3] != "" { + sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + } else { + sv.patch = 0 + } + + // Perform some basic due diligence on the extra parts to ensure they are + // valid. + + if sv.pre != "" { + if err = validatePrerelease(sv.pre); err != nil { + return nil, err + } + } + + if sv.metadata != "" { + if err = validateMetadata(sv.metadata); err != nil { + return nil, err + } + } + + return sv, nil +} + +// MustParse parses a given version and panics on error. +func MustParse(v string) *Version { + sv, err := NewVersion(v) + if err != nil { + panic(err) + } + return sv +} + +// String converts a Version object to a string. +// Note, if the original version contained a leading v this version will not. +// See the Original() method to retrieve the original value. Semantic Versions +// don't contain a leading v per the spec. Instead it's optional on +// implementation. +func (v Version) String() string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original value passed in to be parsed. +func (v *Version) Original() string { + return v.original +} + +// Major returns the major version. +func (v Version) Major() uint64 { + return v.major +} + +// Minor returns the minor version. +func (v Version) Minor() uint64 { + return v.minor +} + +// Patch returns the patch version. +func (v Version) Patch() uint64 { + return v.patch +} + +// Prerelease returns the pre-release version. +func (v Version) Prerelease() string { + return v.pre +} + +// Metadata returns the metadata on the version. +func (v Version) Metadata() string { + return v.metadata +} + +// originalVPrefix returns the original 'v' prefix if any. +func (v Version) originalVPrefix() string { + + // Note, only lowercase v is supported as a prefix by the parser. + if v.original != "" && v.original[:1] == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps current patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrerelease defines the prerelease value. +// Value must not include the required 'hyphen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 { + if err := validatePrerelease(prerelease); err != nil { + return vNext, err + } + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 { + if err := validateMetadata(metadata); err != nil { + return vNext, err + } + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// LessThan tests if one version is less than another one. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// GreaterThan tests if one version is greater than another one. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// Equal tests if two versions are equal to each other. +// Note, versions can be equal with different metadata since metadata +// is not considered part of the comparable version. +func (v *Version) Equal(o *Version) bool { + return v.Compare(o) == 0 +} + +// Compare compares this version to another one. It returns -1, 0, or 1 if +// the version smaller, equal, or larger than the other version. +// +// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is +// lower than the version without a prerelease. Compare always takes into account +// prereleases. If you want to work with ranges using typical range syntaxes that +// skip prereleases if the range is not looking for them use constraints. +func (v *Version) Compare(o *Version) int { + // Compare the major, minor, and patch version for differences. If a + // difference is found return the comparison. + if d := compareSegment(v.Major(), o.Major()); d != 0 { + return d + } + if d := compareSegment(v.Minor(), o.Minor()); d != 0 { + return d + } + if d := compareSegment(v.Patch(), o.Patch()); d != 0 { + return d + } + + // At this point the major, minor, and patch versions are the same. + ps := v.pre + po := o.Prerelease() + + if ps == "" && po == "" { + return 0 + } + if ps == "" { + return 1 + } + if po == "" { + return -1 + } + + return comparePrerelease(ps, po) +} + +// UnmarshalJSON implements JSON.Unmarshaler interface. +func (v *Version) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + return nil +} + +// MarshalJSON implements JSON.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// Scan implements the SQL.Scanner interface. +func (v *Version) Scan(value interface{}) error { + var s string + s, _ = value.(string) + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + return nil +} + +// Value implements the Driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} + +func compareSegment(v, o uint64) int { + if v < o { + return -1 + } + if v > o { + return 1 + } + + return 0 +} + +func comparePrerelease(v, o string) int { + + // split the prelease versions by their part. The separator, per the spec, + // is a . + sparts := strings.Split(v, ".") + oparts := strings.Split(o, ".") + + // Find the longer length of the parts to know how many loop iterations to + // go through. + slen := len(sparts) + olen := len(oparts) + + l := slen + if olen > slen { + l = olen + } + + // Iterate over each part of the prereleases to compare the differences. + for i := 0; i < l; i++ { + // Since the lentgh of the parts can be different we need to create + // a placeholder. This is to avoid out of bounds issues. + stemp := "" + if i < slen { + stemp = sparts[i] + } + + otemp := "" + if i < olen { + otemp = oparts[i] + } + + d := comparePrePart(stemp, otemp) + if d != 0 { + return d + } + } + + // Reaching here means two versions are of equal value but have different + // metadata (the part following a +). They are not identical in string form + // but the version comparison finds them to be equal. + return 0 +} + +func comparePrePart(s, o string) int { + // Fastpath if they are equal + if s == o { + return 0 + } + + // When s or o are empty we can use the other in an attempt to determine + // the response. + if s == "" { + if o != "" { + return -1 + } + return 1 + } + + if o == "" { + if s != "" { + return 1 + } + return -1 + } + + // When comparing strings "99" is greater than "103". To handle + // cases like this we need to detect numbers and compare them. According + // to the semver spec, numbers are always positive. If there is a - at the + // start like -99 this is to be evaluated as an alphanum. numbers always + // have precedence over alphanum. Parsing as Uints because negative numbers + // are ignored. + + oi, n1 := strconv.ParseUint(o, 10, 64) + si, n2 := strconv.ParseUint(s, 10, 64) + + // The case where both are strings compare the strings + if n1 != nil && n2 != nil { + if s > o { + return 1 + } + return -1 + } else if n1 != nil { + // o is a string and s is a number + return -1 + } else if n2 != nil { + // s is a string and o is a number + return 1 + } + // Both are numbers + if si > oi { + return 1 + } + return -1 + +} + +// Like strings.ContainsAny but does an only instead of any. +func containsOnly(s string, comp string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(comp, r) + }) == -1 +} + +// From the spec, "Identifiers MUST comprise only +// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. +// Numeric identifiers MUST NOT include leading zeroes.". These segments can +// be dot separated. +func validatePrerelease(p string) error { + eparts := strings.Split(p, ".") + for _, p := range eparts { + if containsOnly(p, num) { + if len(p) > 1 && p[0] == '0' { + return ErrSegmentStartsZero + } + } else if !containsOnly(p, allowed) { + return ErrInvalidPrerelease + } + } + + return nil +} + +// From the spec, "Build metadata MAY be denoted by +// appending a plus sign and a series of dot separated identifiers immediately +// following the patch or pre-release version. Identifiers MUST comprise only +// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty." +func validateMetadata(m string) error { + eparts := strings.Split(m, ".") + for _, p := range eparts { + if !containsOnly(p, allowed) { + return ErrInvalidMetadata + } + } + return nil +} diff --git a/vendor/github.com/grafana/k6foundry/.gitignore b/vendor/github.com/grafana/k6foundry/.gitignore deleted file mode 100644 index 77fbcf0..0000000 --- a/vendor/github.com/grafana/k6foundry/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -./k6 -.vscode diff --git a/vendor/github.com/grafana/k6foundry/.golangci.yml b/vendor/github.com/grafana/k6foundry/.golangci.yml deleted file mode 100644 index 731a2ea..0000000 --- a/vendor/github.com/grafana/k6foundry/.golangci.yml +++ /dev/null @@ -1,124 +0,0 @@ -# v1.58.2 -# Please don't remove the first line. It uses in CI to determine the golangci version -run: - deadline: 5m - -issues: - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-issues-per-linter: 0 - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. - max-same-issues: 0 - - # We want to try and improve the comments in the k6 codebase, so individual - # non-golint items from the default exclusion list will gradually be added - # to the exclude-rules below - exclude-use-default: false - - exclude-rules: - # Exclude duplicate code and function length and complexity checking in test - # files (due to common repeats and long functions in test code) - - path: _(test|gen)\.go - linters: - - cyclop - - dupl - - gocognit - - funlen - - lll - - forbidigo - -linters-settings: - nolintlint: - # Disable to ensure that nolint directives don't have a leading space. Default is true. - allow-leading-space: false - exhaustive: - default-signifies-exhaustive: true - govet: - check-shadowing: true - cyclop: - max-complexity: 25 - maligned: - suggest-new: true - dupl: - threshold: 150 - goconst: - min-len: 10 - min-occurrences: 4 - funlen: - lines: 80 - statements: 60 - forbidigo: - forbid: - - '^(fmt\\.Print(|f|ln)|print|println)$' - # Forbid everything in os, except os.Signal and os.SyscalError - - '^os\.(.*)$(# Using anything except Signal and SyscallError from the os package is forbidden )?' - # Forbid everything in syscall except the uppercase constants - - '^syscall\.[^A-Z_]+$(# Using anything except constants from the syscall package is forbidden )?' - - '^logrus\.Logger$' - -linters: - disable-all: true - enable: - - asasalint - - asciicheck - - bidichk - - bodyclose - - contextcheck - - cyclop - - dogsled - - dupl - - durationcheck - - errcheck - - errchkjson - - errname - - errorlint - - exhaustive - - exportloopref - - forbidigo - - forcetypeassert - - funlen - - gocheckcompilerdirectives - - gochecknoglobals - - gocognit - - goconst - - gocritic - - gofmt - - gofumpt - - goimports - - gomoddirectives - - goprintffuncname - - gosec - - gosimple - - govet - - importas - - ineffassign - - interfacebloat - - lll - - makezero - - misspell - - nakedret - - nestif - - nilerr - - nilnil - - noctx - - nolintlint - - nosprintfhostport - - paralleltest - - prealloc - - predeclared - - promlinter - - revive - - reassign - - rowserrcheck - - sqlclosecheck - - staticcheck - - stylecheck - - tenv - - tparallel - - typecheck - - unconvert - - unparam - - unused - - usestdlibvars - - wastedassign - - whitespace - fast: false diff --git a/vendor/github.com/grafana/k6foundry/.goreleaser.yaml b/vendor/github.com/grafana/k6foundry/.goreleaser.yaml deleted file mode 100644 index a9c8194..0000000 --- a/vendor/github.com/grafana/k6foundry/.goreleaser.yaml +++ /dev/null @@ -1,38 +0,0 @@ -project_name: k6foundry -before: - hooks: - - go mod tidy -dist: build/dist -builds: - - env: - - CGO_ENABLED=0 - goos: ["darwin", "linux", "windows"] - goarch: ["amd64", "arm64"] - ldflags: - - '-s -w -X main.version={{.Version}}' - dir: cmd/k6foundry -source: - enabled: true - name_template: "{{ .ProjectName }}_{{ .Version }}_source" - -archives: - - id: bundle - format: tar.gz - format_overrides: - - goos: windows - format: zip - -checksum: - name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" - -snapshot: - name_template: "{{ incpatch .Version }}-next+{{.ShortCommit}}{{if .IsGitDirty}}.dirty{{else}}{{end}}" - -changelog: - sort: asc - abbrev: -1 - filters: - exclude: - - "^chore:" - - "^docs:" - - "^test:" diff --git a/vendor/github.com/grafana/k6foundry/LICENSE b/vendor/github.com/grafana/k6foundry/LICENSE deleted file mode 100644 index 0ad25db..0000000 --- a/vendor/github.com/grafana/k6foundry/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/vendor/github.com/grafana/k6foundry/Makefile b/vendor/github.com/grafana/k6foundry/Makefile deleted file mode 100644 index 3d394d8..0000000 --- a/vendor/github.com/grafana/k6foundry/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -work_dir = $(shell pwd) -golangci_version = $(shell head -n 1 .golangci.yml | tr -d '\# ') - -all: build - -.PHONY: build -build: - go build -o build/k6foundry ./cmd/k6foundry - -# Running with -buildvcs=false works around the issue of `go list all` failing when git, which runs as root inside -# the container, refuses to operate on the disruptor source tree as it is not owned by the same user (root). -.PHONY: lint -lint: - docker run --rm -v $(work_dir):/src -w /src -e GOFLAGS=-buildvcs=false golangci/golangci-lint:$(golangci_version) golangci-lint run - -.PHONY: test -test: - go test -race ./... - diff --git a/vendor/github.com/grafana/k6foundry/README.md b/vendor/github.com/grafana/k6foundry/README.md deleted file mode 100644 index 7deb04d..0000000 --- a/vendor/github.com/grafana/k6foundry/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# k6foundry - -`k6foundry` is a CLI for building custom k6 binaries with extensions. - -## Prerequisites - -A Go language tool chain or a properly configured. - -## Installation - -### Using go toolchain - -If you have a go development environment, the installation can be done with the following command: - -``` -go install github.com/grafana/k6foundry@latest -``` - -## Usage - -### build - -The `build` command builds a custom k6 binary with extensions. Multiple extensions with their versions can be specified. The version of k6 can also be specified. If version is omitted, the `latest` version is used. - -The custom binary can target an specific platform, specified as a `os/arch` pair. By default the platform of the `k6foundry` executable is used as target platform. - -The following example shows the options for building a custom k6 `v.0.50.0` binary with the latest version of the kubernetes extension and kafka output extension `v0.7.0`. - -``` -k6foundry build -v v0.50.0 -d github.com/grafana/xk6-kubernetes -d github.com/grafana/xk6-output-kafka@v0.7.0 -``` - -For more examples run - -``` -k6foundry build --help -``` \ No newline at end of file diff --git a/vendor/github.com/grafana/k6foundry/build.go b/vendor/github.com/grafana/k6foundry/build.go deleted file mode 100644 index d498adc..0000000 --- a/vendor/github.com/grafana/k6foundry/build.go +++ /dev/null @@ -1,26 +0,0 @@ -// Package k6foundry contains logic for building k6 binary -package k6foundry - -import ( - "context" - "io" -) - -// BuildInfo describes the binary -type BuildInfo struct { - Platform string - ModVersions map[string]string -} - -// Builder defines the interface for building a k6 binary -type Builder interface { - // Build returns a custom k6 binary for the given version including a set of dependencies - Build( - ctx context.Context, - platform Platform, - k6Version string, - mods []Module, - buildOpts []string, - out io.Writer, - ) (*BuildInfo, error) -} diff --git a/vendor/github.com/grafana/k6foundry/goenv.go b/vendor/github.com/grafana/k6foundry/goenv.go deleted file mode 100644 index eca5182..0000000 --- a/vendor/github.com/grafana/k6foundry/goenv.go +++ /dev/null @@ -1,361 +0,0 @@ -//nolint:revive,forbidigo -package k6foundry - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "maps" - "os" - "os/exec" - "strings" - "time" -) - -var ( - // Error compiling binary - ErrCompiling = errors.New("compiling") - // Error executing go command - ErrExecutingGoCommand = errors.New("executing go command") - // Go toolchacin is not installed - ErrNoGoToolchain = errors.New("go toolchain notfound") - // Git is not installed - ErrNoGit = errors.New("git notfound") - // Error resolving dependency - ErrResolvingDependency = errors.New("resolving dependency") - // Error initiailizing go build environment - ErrSettingGoEnv = errors.New("setting go environment") -) - -// GoOpts defines the options for the go build environment -type GoOpts struct { - // Environment variables passed to the build service - // Can override variables copied from the current go environment - Env map[string]string - // Copy Environment variables to go build environment - CopyGoEnv bool - // Timeout for getting modules - GoGetTimeout time.Duration - // Timeout for building binary - GOBuildTimeout time.Duration - // Use an ephemeral cache. Ignores GoModCache and GoCache - TmpCache bool -} - -type goEnv struct { - env []string - workDir string - platform Platform - stdout io.Writer - stderr io.Writer - tmpDirs []string - tmpCache bool - buildTimeout time.Duration - getTimeout time.Duration -} - -func newGoEnv( - workDir string, - opts GoOpts, - platform Platform, - stdout io.Writer, - stderr io.Writer, -) (*goEnv, error) { - var ( - err error - tmpDirs []string - ) - - if _, hasGo := goVersion(); !hasGo { - return nil, ErrNoGoToolchain - } - - if !hasGit() { - return nil, ErrNoGit - } - - env := map[string]string{} - - // copy current go environment - if opts.CopyGoEnv { - env, err = getGoEnv() - if err != nil { - return nil, fmt.Errorf("copying go environment %w", err) - } - } - - // set/override environment variables - maps.Copy(env, opts.Env) - - if opts.TmpCache { - // override caches with temporary files - var modCache, goCache string - modCache, err = os.MkdirTemp(os.TempDir(), "modcache*") - if err != nil { - return nil, fmt.Errorf("creating mod cache %w", err) - } - - goCache, err = os.MkdirTemp(os.TempDir(), "cache*") - if err != nil { - return nil, fmt.Errorf("creating go cache %w", err) - } - - env["GOCACHE"] = goCache - env["GOMODCACHE"] = modCache - - // add to the list of directories for cleanup - tmpDirs = append(tmpDirs, goCache, modCache) - } - - // ensure path is set - env["PATH"] = os.Getenv("PATH") - - // override platform - env["GOOS"] = platform.OS - env["GOARCH"] = platform.Arch - - // disable CGO if target platform is different from host platform - if env["GOHOSTARCH"] != platform.Arch || env["GOHOSTOS"] != platform.OS { - env["CGO_ENABLED"] = "0" - } - - return &goEnv{ - env: mapToSlice(env), - platform: platform, - workDir: workDir, - stdout: stdout, - stderr: stderr, - buildTimeout: opts.GOBuildTimeout, - getTimeout: opts.GoGetTimeout, - tmpDirs: tmpDirs, - tmpCache: opts.TmpCache, - }, nil -} - -func (e goEnv) close(ctx context.Context) error { - var err error - - if e.tmpCache { - // clean caches, otherwirse directories can't be deleted - err = e.clean(ctx) - } - - // creal all temporary dirs - for _, dir := range e.tmpDirs { - err = errors.Join( - err, - os.RemoveAll(dir), - ) - } - - return err -} - -func (e goEnv) runGo(ctx context.Context, timeout time.Duration, args ...string) error { - cmd := exec.Command("go", args...) - - cmd.Env = e.env - cmd.Dir = e.workDir - - cmd.Stdout = e.stdout - cmd.Stderr = e.stderr - - if timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, timeout) - defer cancel() - } - - // start the command; if it fails to start, report error immediately - err := cmd.Start() - if err != nil { - return fmt.Errorf("%w: %s", ErrExecutingGoCommand, err.Error()) - } - - // wait for the command in a goroutine; the reason for this is - // very subtle: if, in our select, we do `case cmdErr := <-cmd.Wait()`, - // then that case would be chosen immediately, because cmd.Wait() is - // immediately available (even though it blocks for potentially a long - // time, it can be evaluated immediately). So we have to remove that - // evaluation from the `case` statement. - cmdErrChan := make(chan error) - go func() { - cmdErr := cmd.Wait() - if cmdErr != nil { - cmdErr = fmt.Errorf("%w: %s", ErrExecutingGoCommand, cmdErr.Error()) - } - cmdErrChan <- cmdErr - }() - - // unblock either when the command finishes, or when the done - // channel is closed -- whichever comes first - select { - case cmdErr := <-cmdErrChan: - // process ended; report any error immediately - return cmdErr - case <-ctx.Done(): - // context was canceled, either due to timeout or - // maybe a signal from higher up canceled the parent - // context; presumably, the OS also sent the signal - // to the child process, so wait for it to die - select { - // TODO: check this magic timeout - case <-time.After(15 * time.Second): - _ = cmd.Process.Kill() - case <-cmdErrChan: - } - return ctx.Err() - } -} - -func (e goEnv) modInit(ctx context.Context) error { - // initialize the go module - // TODO: change magic constant in timeout - err := e.runGo(ctx, 10*time.Second, "mod", "init", "k6") - if err != nil { - return fmt.Errorf("%w: %s", ErrSettingGoEnv, err.Error()) - } - - return nil -} - -// tidy the module to ensure go.mod will not have versions such as `latest` -func (e goEnv) modTidy(ctx context.Context) error { - err := e.runGo(ctx, e.getTimeout, "mod", "tidy", "-compat=1.17") - if err != nil { - return fmt.Errorf("%w: %s", ErrResolvingDependency, err.Error()) - } - - return nil -} - -func (e goEnv) modRequire(ctx context.Context, modulePath, moduleVersion string) error { - if moduleVersion != "" { - modulePath += "@" + moduleVersion - } - - err := e.runGo(ctx, e.getTimeout, "mod", "edit", "-require", modulePath) - if err != nil { - return fmt.Errorf("%w: %s", ErrResolvingDependency, err.Error()) - } - - return nil -} - -func (e goEnv) modReplace(ctx context.Context, modulePath, moduleVersion, replacePath, replaceVersion string) error { - if moduleVersion != "" { - modulePath += "@" + moduleVersion - } - - if replaceVersion != "" { - replacePath += "@" + replaceVersion - } - - err := e.runGo(ctx, e.getTimeout, "mod", "edit", "-replace", fmt.Sprintf("%s=%s", modulePath, replacePath)) - if err != nil { - return fmt.Errorf("%w: %s", ErrResolvingDependency, err.Error()) - } - - return nil -} - -func (e goEnv) compile(ctx context.Context, outPath string, buildFlags ...string) error { - args := append([]string{"build", "-o", outPath}, buildFlags...) - - err := e.runGo(ctx, e.buildTimeout, args...) - if err != nil { - return fmt.Errorf("%w: %s", ErrCompiling, err.Error()) - } - - return err -} - -func (e goEnv) clean(ctx context.Context) error { - err := e.runGo(ctx, e.buildTimeout, "clean", "-cache", "-modcache") - if err != nil { - return fmt.Errorf("cleaning: %s", err.Error()) - } - - return err -} - -func (e goEnv) modVersion(_ context.Context, mod string) (string, error) { - // can't use runGo because we need the output - cmd := exec.Command("go", "list", "-f", "{{.Version}}", "-m", mod) - cmd.Env = e.env - cmd.Dir = e.workDir - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("list module %s", err.Error()) - } - - // list will return '\n' - return strings.Trim(string(out), "\n"), nil -} - -func mapToSlice(m map[string]string) []string { - s := []string{} - for k, v := range m { - s = append(s, fmt.Sprintf("%s=%s", k, v)) - } - - return s -} - -func goVersion() (string, bool) { - cmd, err := exec.LookPath("go") - if err != nil { - return "", false - } - - out, err := exec.Command(cmd, "version").Output() //nolint:gosec - if err != nil { - return "", false - } - - pre := []byte("go") - - fields := bytes.SplitN(out, []byte{' '}, 4) - if len(fields) < 4 || !bytes.Equal(fields[0], pre) || !bytes.HasPrefix(fields[2], pre) { - return "", false - } - - ver := string(bytes.TrimPrefix(fields[2], pre)) - - return ver, true -} - -func getGoEnv() (map[string]string, error) { - cmd, err := exec.LookPath("go") - if err != nil { - return nil, fmt.Errorf("getting go binary %w", err) - } - - out, err := exec.Command(cmd, "env", "-json").Output() //nolint:gosec - if err != nil { - return nil, fmt.Errorf("getting go env %w", err) - } - - envMap := map[string]string{} - - err = json.Unmarshal(out, &envMap) - if err != nil { - return nil, fmt.Errorf("getting go env %w", err) - } - - return envMap, err -} - -func hasGit() bool { - cmd, err := exec.LookPath("git") - if err != nil { - return false - } - - _, err = exec.Command(cmd, "version").Output() //nolint:gosec - - return err == nil -} diff --git a/vendor/github.com/grafana/k6foundry/module.go b/vendor/github.com/grafana/k6foundry/module.go deleted file mode 100644 index 37336ba..0000000 --- a/vendor/github.com/grafana/k6foundry/module.go +++ /dev/null @@ -1,158 +0,0 @@ -package k6foundry - -import ( - "errors" - "fmt" - "path/filepath" - "regexp" - "strings" - - "golang.org/x/mod/module" - "golang.org/x/mod/semver" -) - -var ( - moduleVersionRegexp = regexp.MustCompile(`.+/v(\d+)$`) - - ErrInvalidDependencyFormat = errors.New("invalid dependency format") //nolint:revive -) - -// Module reference a go module and its version -type Module struct { - // The name (import path) of the go module. If at a version > 1, - // it should contain semantic import version (i.e. "/v2"). - // Used with `go get`. - Path string - - // The version of the Go module, as used with `go get`. - Version string - - // Module replacement - ReplacePath string - - // Module replace version - ReplaceVersion string -} - -func (m Module) String() string { - replace := "" - if m.ReplacePath != "" { - replaceVer := "" - if m.ReplaceVersion != "" { - replaceVer = "@" + m.ReplaceVersion - } - replace = fmt.Sprintf(" => %s%s", m.ReplacePath, replaceVer) - } - return fmt.Sprintf("%s@%s%s", m.Path, m.Version, replace) -} - -// ParseModule parses a module from a string of the form path[@version][=replace[@version]] -func ParseModule(modString string) (Module, error) { - mod, replaceMod, _ := strings.Cut(modString, "=") - - path, version, err := splitPathVersion(mod) - if err != nil { - return Module{}, fmt.Errorf("%w: %q", err, mod) - } - - if err = module.CheckPath(path); err != nil { - return Module{}, fmt.Errorf("%w: %w", ErrInvalidDependencyFormat, err) - } - - if version == "" { - version = "latest" - } - - // TODO: should we enforce the versioned path or reject if it not conformant? - path, err = versionedPath(path, version) - if err != nil { - return Module{}, fmt.Errorf("%w: %q", err, mod) - } - - replacePath, replaceVersion, err := replace(replaceMod) - if err != nil { - return Module{}, err - } - - return Module{ - Path: path, - Version: version, - ReplacePath: replacePath, - ReplaceVersion: replaceVersion, - }, nil -} - -func replace(replaceMod string) (string, string, error) { - if replaceMod == "" { - return "", "", nil - } - - replacePath, replaceVersion, err := splitPathVersion(replaceMod) - if err != nil { - return "", "", err - } - - // is a relative path - if strings.HasPrefix(replacePath, ".") { - if replaceVersion != "" { - return "", "", fmt.Errorf("%w: relative replace path can't specify version", ErrInvalidDependencyFormat) - } - return replacePath, replaceVersion, nil - } - - return replacePath, replaceVersion, nil -} - -// splits a path[@version] string into its components -func splitPathVersion(mod string) (string, string, error) { - path, version, found := strings.Cut(mod, "@") - - // TODO: add regexp for checking path@version - if path == "" || (found && version == "") { - return "", "", fmt.Errorf("%w: %q", ErrInvalidDependencyFormat, mod) - } - - switch version { - case "", "latest": - break - default: - if !semver.IsValid(version) { - return "", "", fmt.Errorf("%w: invalid semantic version %q", ErrInvalidDependencyFormat, mod) - } - version = semver.Canonical(version) - } - - return path, version, nil -} - -// VersionedPath returns a module path with the major component of version added, -// if it is a valid semantic version and is > 1 -// Examples: -// - Path="foo" and Version="v1.0.0" returns "foo" -// - Path="foo" and Version="v2.0.0" returns "foo/v2" -// - Path="foo/v2" and vVersion="v3.0.0" returns an error -// - Path="foo" and Version="latest" returns "foo" -func versionedPath(path string, version string) (string, error) { - // if not is a semantic version return (could have been a commit SHA or 'latest') - if !semver.IsValid(version) { - return path, nil - } - major := semver.Major(version) - - // if the module path has a major version at the end, check for inconsistencies - if moduleVersionRegexp.MatchString(path) { - modPathVer := filepath.Base(path) - if modPathVer != major { - return "", fmt.Errorf("invalid version for versioned path %q: %q", path, version) - } - return path, nil - } - - // if module path does not specify major version, add it if > 1 - switch major { - case "v0", "v1": - return path, nil - default: - return filepath.Join(path, major), nil - } -} diff --git a/vendor/github.com/grafana/k6foundry/native.go b/vendor/github.com/grafana/k6foundry/native.go deleted file mode 100644 index 23e0610..0000000 --- a/vendor/github.com/grafana/k6foundry/native.go +++ /dev/null @@ -1,278 +0,0 @@ -//nolint:forbidigo,revive,funlen -package k6foundry - -import ( - "context" - "fmt" - "io" - "log/slog" - "os" - "path/filepath" - "strings" -) - -const ( - defaultK6ModulePath = "go.k6.io/k6" - - defaultWorkDir = "k6foundry*" - - mainModuleTemplate = `package main - -import ( - k6cmd "%s/cmd" - -) - -func main() { - k6cmd.Execute() -} -` - modImportTemplate = `package main - - import _ %q -` -) - -type nativeBuilder struct { - NativeBuilderOpts - log *slog.Logger -} - -// NativeBuilderOpts defines the options for the Native build environment -type NativeBuilderOpts struct { - // options used for running go - GoOpts - // use alternative k6 repository - K6Repo string - // don't cleanup work environment (useful for debugging) - SkipCleanup bool - // redirect stdout - Stdout io.Writer - // redirect stderr - Stderr io.Writer - // set log level (INFO, WARN, ERROR) - Logger *slog.Logger -} - -// NewDefaultNativeBuilder creates a new native build environment with default options -func NewDefaultNativeBuilder() (Builder, error) { - return NewNativeBuilder( - context.TODO(), - NativeBuilderOpts{ - GoOpts: GoOpts{ - CopyGoEnv: true, - }, - }, - ) -} - -// NewNativeBuilder creates a new native build environment with the given options -func NewNativeBuilder(_ context.Context, opts NativeBuilderOpts) (Builder, error) { - if opts.Stderr == nil { - opts.Stderr = io.Discard - } - - if opts.Stdout == nil { - opts.Stdout = io.Discard - } - - // set default logger if none passed - log := opts.Logger - if log == nil { - log = slog.New( - slog.NewTextHandler( - opts.Stderr, - &slog.HandlerOptions{}, - ), - ) - } - - return &nativeBuilder{ - NativeBuilderOpts: opts, - log: log, - }, nil -} - -// Build builds a custom k6 binary for a target platform with the given dependencies into the out io.Writer -func (b *nativeBuilder) Build( - ctx context.Context, - platform Platform, - k6Version string, - exts []Module, - buildOpts []string, - binary io.Writer, -) (*BuildInfo, error) { - workDir, err := os.MkdirTemp(os.TempDir(), defaultWorkDir) - if err != nil { - return nil, fmt.Errorf("creating working directory: %w", err) - } - - defer func() { - if b.SkipCleanup { - b.log.Info(fmt.Sprintf("Skipping cleanup. leaving directory %s intact", workDir)) - return - } - - b.log.Info(fmt.Sprintf("Cleaning up work directory %s", workDir)) - _ = os.RemoveAll(workDir) - }() - - // prepare the build environment - b.log.Info("Building new k6 binary (native)") - - k6Binary := filepath.Join(workDir, "k6") - - buildEnv, err := newGoEnv( - workDir, - b.GoOpts, - platform, - b.Stdout, - b.Stderr, - ) - if err != nil { - return nil, err - } - - defer func() { - if b.SkipCleanup { - b.log.Info("Skipping go cleanup") - return - } - _ = buildEnv.close(ctx) - }() - - buildInfo := &BuildInfo{ - Platform: platform.String(), - ModVersions: map[string]string{}, - } - - b.log.Info("Initializing Go module") - err = buildEnv.modInit(ctx) - if err != nil { - return nil, err - } - - b.log.Info("Creating k6 main") - err = b.createMain(ctx, workDir) - if err != nil { - return nil, err - } - - k6Mod := Module{ - Path: defaultK6ModulePath, - Version: k6Version, - ReplacePath: b.K6Repo, - } - - modVer, err := b.addMod(ctx, buildEnv, k6Mod) - if err != nil { - return nil, err - } - - buildInfo.ModVersions[defaultK6ModulePath] = modVer - - b.log.Info("importing extensions") - for _, m := range exts { - err = b.createModuleImport(ctx, workDir, m) - if err != nil { - return nil, err - } - - modVer, err = b.addMod(ctx, buildEnv, m) - if err != nil { - return nil, err - } - buildInfo.ModVersions[m.Path] = modVer - } - - b.log.Info("Building k6") - err = buildEnv.compile(ctx, k6Binary, buildOpts...) - if err != nil { - return nil, err - } - - b.log.Info("Build complete") - k6File, err := os.Open(k6Binary) //nolint:gosec - if err != nil { - return nil, err - } - - _, err = io.Copy(binary, k6File) - if err != nil { - return nil, fmt.Errorf("copying binary %w", err) - } - - return buildInfo, nil -} - -func (b *nativeBuilder) createMain(_ context.Context, path string) error { - // write the main module file - mainPath := filepath.Join(path, "main.go") - mainContent := fmt.Sprintf(mainModuleTemplate, defaultK6ModulePath) - err := os.WriteFile(mainPath, []byte(mainContent), 0o600) - if err != nil { - return fmt.Errorf("writing main file %w", err) - } - - return nil -} - -func (b *nativeBuilder) addMod(ctx context.Context, e *goEnv, mod Module) (string, error) { - b.log.Info(fmt.Sprintf("adding dependency %s", mod.String())) - - if mod.ReplacePath == "" { - if err := e.modRequire(ctx, mod.Path, mod.Version); err != nil { - return "", err - } - - if err := e.modTidy(ctx); err != nil { - return "", err - } - - return e.modVersion(ctx, mod.Path) - } - - // resolve path to and absolute path because the mod replace will occur in the work directory - replacePath, err := resolvePath(mod.ReplacePath) - if err != nil { - return "", fmt.Errorf("resolving replace path: %w", err) - } - - if err := e.modReplace(ctx, mod.Path, mod.Version, replacePath, mod.ReplaceVersion); err != nil { - return "", err - } - - if err := e.modTidy(ctx); err != nil { - return "", err - } - - return e.modVersion(ctx, mod.Path) -} - -func resolvePath(path string) (string, error) { - var err error - // expand environment variables - if strings.Contains(path, "$") { - path = os.ExpandEnv(path) - } - - if strings.HasPrefix(path, ".") { - path, err = filepath.Abs(path) - if err != nil { - return "", err - } - } - - return path, nil -} - -func (b *nativeBuilder) createModuleImport(_ context.Context, path string, mod Module) error { - modImportFile := filepath.Join(path, strings.ReplaceAll(mod.Path, "/", "_")+".go") - modImportContent := fmt.Sprintf(modImportTemplate, mod.Path) - err := os.WriteFile(modImportFile, []byte(modImportContent), 0o600) - if err != nil { - return fmt.Errorf("writing mod file %w", err) - } - - return nil -} diff --git a/vendor/github.com/grafana/k6foundry/platform.go b/vendor/github.com/grafana/k6foundry/platform.go deleted file mode 100644 index 0ac18fe..0000000 --- a/vendor/github.com/grafana/k6foundry/platform.go +++ /dev/null @@ -1,66 +0,0 @@ -package k6foundry - -import ( - "errors" - "fmt" - "runtime" - "strings" -) - -var ErrInvalidPlatform = errors.New("invalid platform") //nolint:revive - -// Platform defines a target OS and architecture for building a custom binary -type Platform struct { - OS string - Arch string -} - -// RuntimePlatform returns the Platform of the current executable -func RuntimePlatform() Platform { - return Platform{OS: runtime.GOOS, Arch: runtime.GOARCH} -} - -// NewPlatform creates a new Platform given the os and arch -func NewPlatform(os, arch string) Platform { - return Platform{OS: os, Arch: arch} -} - -// ParsePlatform parses a string of the format os/arch and returns the corresponding platform -func ParsePlatform(str string) (Platform, error) { - idx := strings.IndexRune(str, '/') - if idx <= 0 || idx == len(str)-1 { - return Platform{}, fmt.Errorf("%w: %s", ErrInvalidPlatform, str) - } - - return NewPlatform(str[:idx], str[idx+1:]), nil -} - -// String returns the platform in the format os/arch -func (p Platform) String() string { - return p.OS + "/" + p.Arch -} - -// Supported indicates is the given platform is supported -func (p Platform) Supported() bool { - for _, plat := range supported { - if plat.OS == p.OS && plat.Arch == p.Arch { - return true - } - } - - return false -} - -// SupportedPlatforms returns a list of supported platforms -func SupportedPlatforms() []Platform { - return supported -} - -var supported = []Platform{ //nolint:gochecknoglobals - {OS: "linux", Arch: "amd64"}, - {OS: "linux", Arch: "arm64"}, - {OS: "windows", Arch: "amd64"}, - {OS: "windows", Arch: "arm64"}, - {OS: "darwin", Arch: "amd64"}, - {OS: "darwin", Arch: "arm64"}, -} diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE deleted file mode 100644 index 2a7cf70..0000000 --- a/vendor/golang.org/x/mod/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright 2009 The Go Authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google LLC nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS deleted file mode 100644 index 7330990..0000000 --- a/vendor/golang.org/x/mod/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go b/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go deleted file mode 100644 index 150f887..0000000 --- a/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go +++ /dev/null @@ -1,78 +0,0 @@ -// 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 lazyregexp is a thin wrapper over regexp, allowing the use of global -// regexp variables without forcing them to be compiled at init. -package lazyregexp - -import ( - "os" - "regexp" - "strings" - "sync" -) - -// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be -// compiled the first time it is needed. -type Regexp struct { - str string - once sync.Once - rx *regexp.Regexp -} - -func (r *Regexp) re() *regexp.Regexp { - r.once.Do(r.build) - return r.rx -} - -func (r *Regexp) build() { - r.rx = regexp.MustCompile(r.str) - r.str = "" -} - -func (r *Regexp) FindSubmatch(s []byte) [][]byte { - return r.re().FindSubmatch(s) -} - -func (r *Regexp) FindStringSubmatch(s string) []string { - return r.re().FindStringSubmatch(s) -} - -func (r *Regexp) FindStringSubmatchIndex(s string) []int { - return r.re().FindStringSubmatchIndex(s) -} - -func (r *Regexp) ReplaceAllString(src, repl string) string { - return r.re().ReplaceAllString(src, repl) -} - -func (r *Regexp) FindString(s string) string { - return r.re().FindString(s) -} - -func (r *Regexp) FindAllString(s string, n int) []string { - return r.re().FindAllString(s, n) -} - -func (r *Regexp) MatchString(s string) bool { - return r.re().MatchString(s) -} - -func (r *Regexp) SubexpNames() []string { - return r.re().SubexpNames() -} - -var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") - -// New creates a new lazy regexp, delaying the compiling work until it is first -// needed. If the code is being run as part of tests, the regexp compiling will -// happen immediately. -func New(str string) *Regexp { - lr := &Regexp{str: str} - if inTest { - // In tests, always compile the regexps early. - lr.re() - } - return lr -} diff --git a/vendor/golang.org/x/mod/module/module.go b/vendor/golang.org/x/mod/module/module.go deleted file mode 100644 index 2a364b2..0000000 --- a/vendor/golang.org/x/mod/module/module.go +++ /dev/null @@ -1,841 +0,0 @@ -// 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 module defines the module.Version type along with support code. -// -// The [module.Version] type is a simple Path, Version pair: -// -// type Version struct { -// Path string -// Version string -// } -// -// There are no restrictions imposed directly by use of this structure, -// but additional checking functions, most notably [Check], verify that -// a particular path, version pair is valid. -// -// # Escaped Paths -// -// Module paths appear as substrings of file system paths -// (in the download cache) and of web server URLs in the proxy protocol. -// In general we cannot rely on file systems to be case-sensitive, -// nor can we rely on web servers, since they read from file systems. -// That is, we cannot rely on the file system to keep rsc.io/QUOTE -// and rsc.io/quote separate. Windows and macOS don't. -// Instead, we must never require two different casings of a file path. -// Because we want the download cache to match the proxy protocol, -// and because we want the proxy protocol to be possible to serve -// from a tree of static files (which might be stored on a case-insensitive -// file system), the proxy protocol must never require two different casings -// of a URL path either. -// -// One possibility would be to make the escaped form be the lowercase -// hexadecimal encoding of the actual path bytes. This would avoid ever -// needing different casings of a file path, but it would be fairly illegible -// to most programmers when those paths appeared in the file system -// (including in file paths in compiler errors and stack traces) -// in web server logs, and so on. Instead, we want a safe escaped form that -// leaves most paths unaltered. -// -// The safe escaped form is to replace every uppercase letter -// with an exclamation mark followed by the letter's lowercase equivalent. -// -// For example, -// -// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. -// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy -// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. -// -// Import paths that avoid upper-case letters are left unchanged. -// Note that because import paths are ASCII-only and avoid various -// problematic punctuation (like : < and >), the escaped form is also ASCII-only -// and avoids the same problematic punctuation. -// -// Import paths have never allowed exclamation marks, so there is no -// need to define how to escape a literal !. -// -// # Unicode Restrictions -// -// Today, paths are disallowed from using Unicode. -// -// Although paths are currently disallowed from using Unicode, -// we would like at some point to allow Unicode letters as well, to assume that -// file systems and URLs are Unicode-safe (storing UTF-8), and apply -// the !-for-uppercase convention for escaping them in the file system. -// But there are at least two subtle considerations. -// -// First, note that not all case-fold equivalent distinct runes -// form an upper/lower pair. -// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) -// are three distinct runes that case-fold to each other. -// When we do add Unicode letters, we must not assume that upper/lower -// are the only case-equivalent pairs. -// Perhaps the Kelvin symbol would be disallowed entirely, for example. -// Or perhaps it would escape as "!!k", or perhaps as "(212A)". -// -// Second, it would be nice to allow Unicode marks as well as letters, -// but marks include combining marks, and then we must deal not -// only with case folding but also normalization: both U+00E9 ('é') -// and U+0065 U+0301 ('e' followed by combining acute accent) -// look the same on the page and are treated by some file systems -// as the same path. If we do allow Unicode marks in paths, there -// must be some kind of normalization to allow only one canonical -// encoding of any character used in an import path. -package module - -// IMPORTANT NOTE -// -// This file essentially defines the set of valid import paths for the go command. -// There are many subtle considerations, including Unicode ambiguity, -// security, network, and file system representations. -// -// This file also defines the set of valid module path and version combinations, -// another topic with many subtle considerations. -// -// Changes to the semantics in this file require approval from rsc. - -import ( - "errors" - "fmt" - "path" - "sort" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/mod/semver" -) - -// A Version (for clients, a module.Version) is defined by a module path and version pair. -// These are stored in their plain (unescaped) form. -type Version struct { - // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2". - Path string - - // Version is usually a semantic version in canonical form. - // There are three exceptions to this general rule. - // First, the top-level target of a build has no specific version - // and uses Version = "". - // Second, during MVS calculations the version "none" is used - // to represent the decision to take no version of a given module. - // Third, filesystem paths found in "replace" directives are - // represented by a path with an empty version. - Version string `json:",omitempty"` -} - -// String returns a representation of the Version suitable for logging -// (Path@Version, or just Path if Version is empty). -func (m Version) String() string { - if m.Version == "" { - return m.Path - } - return m.Path + "@" + m.Version -} - -// A ModuleError indicates an error specific to a module. -type ModuleError struct { - Path string - Version string - Err error -} - -// VersionError returns a [ModuleError] derived from a [Version] and error, -// or err itself if it is already such an error. -func VersionError(v Version, err error) error { - var mErr *ModuleError - if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version { - return err - } - return &ModuleError{ - Path: v.Path, - Version: v.Version, - Err: err, - } -} - -func (e *ModuleError) Error() string { - if v, ok := e.Err.(*InvalidVersionError); ok { - return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err) - } - if e.Version != "" { - return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err) - } - return fmt.Sprintf("module %s: %v", e.Path, e.Err) -} - -func (e *ModuleError) Unwrap() error { return e.Err } - -// An InvalidVersionError indicates an error specific to a version, with the -// module path unknown or specified externally. -// -// A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError -// must not wrap a ModuleError. -type InvalidVersionError struct { - Version string - Pseudo bool - Err error -} - -// noun returns either "version" or "pseudo-version", depending on whether -// e.Version is a pseudo-version. -func (e *InvalidVersionError) noun() string { - if e.Pseudo { - return "pseudo-version" - } - return "version" -} - -func (e *InvalidVersionError) Error() string { - return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err) -} - -func (e *InvalidVersionError) Unwrap() error { return e.Err } - -// An InvalidPathError indicates a module, import, or file path doesn't -// satisfy all naming constraints. See [CheckPath], [CheckImportPath], -// and [CheckFilePath] for specific restrictions. -type InvalidPathError struct { - Kind string // "module", "import", or "file" - Path string - Err error -} - -func (e *InvalidPathError) Error() string { - return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err) -} - -func (e *InvalidPathError) Unwrap() error { return e.Err } - -// Check checks that a given module path, version pair is valid. -// In addition to the path being a valid module path -// and the version being a valid semantic version, -// the two must correspond. -// For example, the path "yaml/v2" only corresponds to -// semantic versions beginning with "v2.". -func Check(path, version string) error { - if err := CheckPath(path); err != nil { - return err - } - if !semver.IsValid(version) { - return &ModuleError{ - Path: path, - Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")}, - } - } - _, pathMajor, _ := SplitPathVersion(path) - if err := CheckPathMajor(version, pathMajor); err != nil { - return &ModuleError{Path: path, Err: err} - } - return nil -} - -// firstPathOK reports whether r can appear in the first element of a module path. -// The first element of the path must be an LDH domain name, at least for now. -// To avoid case ambiguity, the domain name must be entirely lower case. -func firstPathOK(r rune) bool { - return r == '-' || r == '.' || - '0' <= r && r <= '9' || - 'a' <= r && r <= 'z' -} - -// modPathOK reports whether r can appear in a module path element. -// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. -// -// This matches what "go get" has historically recognized in import paths, -// and avoids confusing sequences like '%20' or '+' that would change meaning -// if used in a URL. -// -// TODO(rsc): We would like to allow Unicode letters, but that requires additional -// care in the safe encoding (see "escaped paths" above). -func modPathOK(r rune) bool { - if r < utf8.RuneSelf { - return r == '-' || r == '.' || r == '_' || r == '~' || - '0' <= r && r <= '9' || - 'A' <= r && r <= 'Z' || - 'a' <= r && r <= 'z' - } - return false -} - -// importPathOK reports whether r can appear in a package import path element. -// -// Import paths are intermediate between module paths and file paths: we allow -// disallow characters that would be confusing or ambiguous as arguments to -// 'go get' (such as '@' and ' ' ), but allow certain characters that are -// otherwise-unambiguous on the command line and historically used for some -// binary names (such as '++' as a suffix for compiler binaries and wrappers). -func importPathOK(r rune) bool { - return modPathOK(r) || r == '+' -} - -// fileNameOK reports whether r can appear in a file name. -// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. -// If we expand the set of allowed characters here, we have to -// work harder at detecting potential case-folding and normalization collisions. -// See note about "escaped paths" above. -func fileNameOK(r rune) bool { - if r < utf8.RuneSelf { - // Entire set of ASCII punctuation, from which we remove characters: - // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ - // We disallow some shell special characters: " ' * < > ? ` | - // (Note that some of those are disallowed by the Windows file system as well.) - // We also disallow path separators / : and \ (fileNameOK is only called on path element characters). - // We allow spaces (U+0020) in file names. - const allowed = "!#$%&()+,-.=@[]^_{}~ " - if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' { - return true - } - return strings.ContainsRune(allowed, r) - } - // It may be OK to add more ASCII punctuation here, but only carefully. - // For example Windows disallows < > \, and macOS disallows :, so we must not allow those. - return unicode.IsLetter(r) -} - -// CheckPath checks that a module path is valid. -// A valid module path is a valid import path, as checked by [CheckImportPath], -// with three additional constraints. -// First, the leading path element (up to the first slash, if any), -// by convention a domain name, must contain only lower-case ASCII letters, -// ASCII digits, dots (U+002E), and dashes (U+002D); -// it must contain at least one dot and cannot start with a dash. -// Second, for a final path element of the form /vN, where N looks numeric -// (ASCII digits and dots) must not begin with a leading zero, must not be /v1, -// and must not contain any dots. For paths beginning with "gopkg.in/", -// this second requirement is replaced by a requirement that the path -// follow the gopkg.in server's conventions. -// Third, no path element may begin with a dot. -func CheckPath(path string) (err error) { - defer func() { - if err != nil { - err = &InvalidPathError{Kind: "module", Path: path, Err: err} - } - }() - - if err := checkPath(path, modulePath); err != nil { - return err - } - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - if i == 0 { - return fmt.Errorf("leading slash") - } - if !strings.Contains(path[:i], ".") { - return fmt.Errorf("missing dot in first path element") - } - if path[0] == '-' { - return fmt.Errorf("leading dash in first path element") - } - for _, r := range path[:i] { - if !firstPathOK(r) { - return fmt.Errorf("invalid char %q in first path element", r) - } - } - if _, _, ok := SplitPathVersion(path); !ok { - return fmt.Errorf("invalid version") - } - return nil -} - -// CheckImportPath checks that an import path is valid. -// -// A valid import path consists of one or more valid path elements -// separated by slashes (U+002F). (It must not begin with nor end in a slash.) -// -// A valid path element is a non-empty string made up of -// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. -// It must not end with a dot (U+002E), nor contain two dots in a row. -// -// The element prefix up to the first dot must not be a reserved file name -// on Windows, regardless of case (CON, com1, NuL, and so on). The element -// must not have a suffix of a tilde followed by one or more ASCII digits -// (to exclude paths elements that look like Windows short-names). -// -// CheckImportPath may be less restrictive in the future, but see the -// top-level package documentation for additional information about -// subtleties of Unicode. -func CheckImportPath(path string) error { - if err := checkPath(path, importPath); err != nil { - return &InvalidPathError{Kind: "import", Path: path, Err: err} - } - return nil -} - -// pathKind indicates what kind of path we're checking. Module paths, -// import paths, and file paths have different restrictions. -type pathKind int - -const ( - modulePath pathKind = iota - importPath - filePath -) - -// checkPath checks that a general path is valid. kind indicates what -// specific constraints should be applied. -// -// checkPath returns an error describing why the path is not valid. -// Because these checks apply to module, import, and file paths, -// and because other checks may be applied, the caller is expected to wrap -// this error with [InvalidPathError]. -func checkPath(path string, kind pathKind) error { - if !utf8.ValidString(path) { - return fmt.Errorf("invalid UTF-8") - } - if path == "" { - return fmt.Errorf("empty string") - } - if path[0] == '-' && kind != filePath { - return fmt.Errorf("leading dash") - } - if strings.Contains(path, "//") { - return fmt.Errorf("double slash") - } - if path[len(path)-1] == '/' { - return fmt.Errorf("trailing slash") - } - elemStart := 0 - for i, r := range path { - if r == '/' { - if err := checkElem(path[elemStart:i], kind); err != nil { - return err - } - elemStart = i + 1 - } - } - if err := checkElem(path[elemStart:], kind); err != nil { - return err - } - return nil -} - -// checkElem checks whether an individual path element is valid. -func checkElem(elem string, kind pathKind) error { - if elem == "" { - return fmt.Errorf("empty path element") - } - if strings.Count(elem, ".") == len(elem) { - return fmt.Errorf("invalid path element %q", elem) - } - if elem[0] == '.' && kind == modulePath { - return fmt.Errorf("leading dot in path element") - } - if elem[len(elem)-1] == '.' { - return fmt.Errorf("trailing dot in path element") - } - for _, r := range elem { - ok := false - switch kind { - case modulePath: - ok = modPathOK(r) - case importPath: - ok = importPathOK(r) - case filePath: - ok = fileNameOK(r) - default: - panic(fmt.Sprintf("internal error: invalid kind %v", kind)) - } - if !ok { - return fmt.Errorf("invalid char %q", r) - } - } - - // Windows disallows a bunch of path elements, sadly. - // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file - short := elem - if i := strings.Index(short, "."); i >= 0 { - short = short[:i] - } - for _, bad := range badWindowsNames { - if strings.EqualFold(bad, short) { - return fmt.Errorf("%q disallowed as path element component on Windows", short) - } - } - - if kind == filePath { - // don't check for Windows short-names in file names. They're - // only an issue for import paths. - return nil - } - - // Reject path components that look like Windows short-names. - // Those usually end in a tilde followed by one or more ASCII digits. - if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 { - suffix := short[tilde+1:] - suffixIsDigits := true - for _, r := range suffix { - if r < '0' || r > '9' { - suffixIsDigits = false - break - } - } - if suffixIsDigits { - return fmt.Errorf("trailing tilde and digits in path element") - } - } - - return nil -} - -// CheckFilePath checks that a slash-separated file path is valid. -// The definition of a valid file path is the same as the definition -// of a valid import path except that the set of allowed characters is larger: -// all Unicode letters, ASCII digits, the ASCII space character (U+0020), -// and the ASCII punctuation characters -// “!#$%&()+,-.=@[]^_{}~”. -// (The excluded punctuation characters, " * < > ? ` ' | / \ and :, -// have special meanings in certain shells or operating systems.) -// -// CheckFilePath may be less restrictive in the future, but see the -// top-level package documentation for additional information about -// subtleties of Unicode. -func CheckFilePath(path string) error { - if err := checkPath(path, filePath); err != nil { - return &InvalidPathError{Kind: "file", Path: path, Err: err} - } - return nil -} - -// badWindowsNames are the reserved file path elements on Windows. -// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file -var badWindowsNames = []string{ - "CON", - "PRN", - "AUX", - "NUL", - "COM1", - "COM2", - "COM3", - "COM4", - "COM5", - "COM6", - "COM7", - "COM8", - "COM9", - "LPT1", - "LPT2", - "LPT3", - "LPT4", - "LPT5", - "LPT6", - "LPT7", - "LPT8", - "LPT9", -} - -// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path -// and version is either empty or "/vN" for N >= 2. -// As a special case, gopkg.in paths are recognized directly; -// they require ".vN" instead of "/vN", and for all N, not just N >= 2. -// SplitPathVersion returns with ok = false when presented with -// a path whose last path element does not satisfy the constraints -// applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2". -func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { - if strings.HasPrefix(path, "gopkg.in/") { - return splitGopkgIn(path) - } - - i := len(path) - dot := false - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') { - if path[i-1] == '.' { - dot = true - } - i-- - } - if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' { - return path, "", true - } - prefix, pathMajor = path[:i-2], path[i-2:] - if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" { - return path, "", false - } - return prefix, pathMajor, true -} - -// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths. -func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { - if !strings.HasPrefix(path, "gopkg.in/") { - return path, "", false - } - i := len(path) - if strings.HasSuffix(path, "-unstable") { - i -= len("-unstable") - } - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') { - i-- - } - if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' { - // All gopkg.in paths must end in vN for some N. - return path, "", false - } - prefix, pathMajor = path[:i-2], path[i-2:] - if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" { - return path, "", false - } - return prefix, pathMajor, true -} - -// MatchPathMajor reports whether the semantic version v -// matches the path major version pathMajor. -// -// MatchPathMajor returns true if and only if [CheckPathMajor] returns nil. -func MatchPathMajor(v, pathMajor string) bool { - return CheckPathMajor(v, pathMajor) == nil -} - -// CheckPathMajor returns a non-nil error if the semantic version v -// does not match the path major version pathMajor. -func CheckPathMajor(v, pathMajor string) error { - // TODO(jayconrod): return errors or panic for invalid inputs. This function - // (and others) was covered by integration tests for cmd/go, and surrounding - // code protected against invalid inputs like non-canonical versions. - if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { - pathMajor = strings.TrimSuffix(pathMajor, "-unstable") - } - if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { - // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. - // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. - return nil - } - m := semver.Major(v) - if pathMajor == "" { - if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" { - return nil - } - pathMajor = "v0 or v1" - } else if pathMajor[0] == '/' || pathMajor[0] == '.' { - if m == pathMajor[1:] { - return nil - } - pathMajor = pathMajor[1:] - } - return &InvalidVersionError{ - Version: v, - Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)), - } -} - -// PathMajorPrefix returns the major-version tag prefix implied by pathMajor. -// An empty PathMajorPrefix allows either v0 or v1. -// -// Note that [MatchPathMajor] may accept some versions that do not actually begin -// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' -// pathMajor, even though that pathMajor implies 'v1' tagging. -func PathMajorPrefix(pathMajor string) string { - if pathMajor == "" { - return "" - } - if pathMajor[0] != '/' && pathMajor[0] != '.' { - panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator") - } - if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { - pathMajor = strings.TrimSuffix(pathMajor, "-unstable") - } - m := pathMajor[1:] - if m != semver.Major(m) { - panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version") - } - return m -} - -// CanonicalVersion returns the canonical form of the version string v. -// It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible". -func CanonicalVersion(v string) string { - cv := semver.Canonical(v) - if semver.Build(v) == "+incompatible" { - cv += "+incompatible" - } - return cv -} - -// Sort sorts the list by Path, breaking ties by comparing [Version] fields. -// The Version fields are interpreted as semantic versions (using [semver.Compare]) -// optionally followed by a tie-breaking suffix introduced by a slash character, -// like in "v0.0.1/go.mod". -func Sort(list []Version) { - sort.Slice(list, func(i, j int) bool { - mi := list[i] - mj := list[j] - if mi.Path != mj.Path { - return mi.Path < mj.Path - } - // To help go.sum formatting, allow version/file. - // Compare semver prefix by semver rules, - // file by string order. - vi := mi.Version - vj := mj.Version - var fi, fj string - if k := strings.Index(vi, "/"); k >= 0 { - vi, fi = vi[:k], vi[k:] - } - if k := strings.Index(vj, "/"); k >= 0 { - vj, fj = vj[:k], vj[k:] - } - if vi != vj { - return semver.Compare(vi, vj) < 0 - } - return fi < fj - }) -} - -// EscapePath returns the escaped form of the given module path. -// It fails if the module path is invalid. -func EscapePath(path string) (escaped string, err error) { - if err := CheckPath(path); err != nil { - return "", err - } - - return escapeString(path) -} - -// EscapeVersion returns the escaped form of the given module version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func EscapeVersion(v string) (escaped string, err error) { - if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") { - return "", &InvalidVersionError{ - Version: v, - Err: fmt.Errorf("disallowed version string"), - } - } - return escapeString(v) -} - -func escapeString(s string) (escaped string, err error) { - haveUpper := false - for _, r := range s { - if r == '!' || r >= utf8.RuneSelf { - // This should be disallowed by CheckPath, but diagnose anyway. - // The correctness of the escaping loop below depends on it. - return "", fmt.Errorf("internal error: inconsistency in EscapePath") - } - if 'A' <= r && r <= 'Z' { - haveUpper = true - } - } - - if !haveUpper { - return s, nil - } - - var buf []byte - for _, r := range s { - if 'A' <= r && r <= 'Z' { - buf = append(buf, '!', byte(r+'a'-'A')) - } else { - buf = append(buf, byte(r)) - } - } - return string(buf), nil -} - -// UnescapePath returns the module path for the given escaped path. -// It fails if the escaped path is invalid or describes an invalid path. -func UnescapePath(escaped string) (path string, err error) { - path, ok := unescapeString(escaped) - if !ok { - return "", fmt.Errorf("invalid escaped module path %q", escaped) - } - if err := CheckPath(path); err != nil { - return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err) - } - return path, nil -} - -// UnescapeVersion returns the version string for the given escaped version. -// It fails if the escaped form is invalid or describes an invalid version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func UnescapeVersion(escaped string) (v string, err error) { - v, ok := unescapeString(escaped) - if !ok { - return "", fmt.Errorf("invalid escaped version %q", escaped) - } - if err := checkElem(v, filePath); err != nil { - return "", fmt.Errorf("invalid escaped version %q: %v", v, err) - } - return v, nil -} - -func unescapeString(escaped string) (string, bool) { - var buf []byte - - bang := false - for _, r := range escaped { - if r >= utf8.RuneSelf { - return "", false - } - if bang { - bang = false - if r < 'a' || 'z' < r { - return "", false - } - buf = append(buf, byte(r+'A'-'a')) - continue - } - if r == '!' { - bang = true - continue - } - if 'A' <= r && r <= 'Z' { - return "", false - } - buf = append(buf, byte(r)) - } - if bang { - return "", false - } - return string(buf), true -} - -// MatchPrefixPatterns reports whether any path prefix of target matches one of -// the glob patterns (as defined by [path.Match]) in the comma-separated globs -// list. This implements the algorithm used when matching a module path to the -// GOPRIVATE environment variable, as described by 'go help module-private'. -// -// It ignores any empty or malformed patterns in the list. -// Trailing slashes on patterns are ignored. -func MatchPrefixPatterns(globs, target string) bool { - for globs != "" { - // Extract next non-empty glob in comma-separated list. - var glob string - if i := strings.Index(globs, ","); i >= 0 { - glob, globs = globs[:i], globs[i+1:] - } else { - glob, globs = globs, "" - } - glob = strings.TrimSuffix(glob, "/") - if glob == "" { - continue - } - - // A glob with N+1 path elements (N slashes) needs to be matched - // against the first N+1 path elements of target, - // which end just before the N+1'th slash. - n := strings.Count(glob, "/") - prefix := target - // Walk target, counting slashes, truncating at the N+1'th slash. - for i := 0; i < len(target); i++ { - if target[i] == '/' { - if n == 0 { - prefix = target[:i] - break - } - n-- - } - } - if n > 0 { - // Not enough prefix elements. - continue - } - matched, _ := path.Match(glob, prefix) - if matched { - return true - } - } - return false -} diff --git a/vendor/golang.org/x/mod/module/pseudo.go b/vendor/golang.org/x/mod/module/pseudo.go deleted file mode 100644 index 9cf19d3..0000000 --- a/vendor/golang.org/x/mod/module/pseudo.go +++ /dev/null @@ -1,250 +0,0 @@ -// 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. - -// Pseudo-versions -// -// Code authors are expected to tag the revisions they want users to use, -// including prereleases. However, not all authors tag versions at all, -// and not all commits a user might want to try will have tags. -// A pseudo-version is a version with a special form that allows us to -// address an untagged commit and order that version with respect to -// other versions we might encounter. -// -// A pseudo-version takes one of the general forms: -// -// (1) vX.0.0-yyyymmddhhmmss-abcdef123456 -// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 -// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible -// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 -// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible -// -// If there is no recently tagged version with the right major version vX, -// then form (1) is used, creating a space of pseudo-versions at the bottom -// of the vX version range, less than any tagged version, including the unlikely v0.0.0. -// -// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible, -// then the pseudo-version uses form (2) or (3), making it a prerelease for the next -// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string -// ensures that the pseudo-version compares less than possible future explicit prereleases -// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1. -// -// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible, -// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease. - -package module - -import ( - "errors" - "fmt" - "strings" - "time" - - "golang.org/x/mod/internal/lazyregexp" - "golang.org/x/mod/semver" -) - -var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`) - -const PseudoVersionTimestampFormat = "20060102150405" - -// PseudoVersion returns a pseudo-version for the given major version ("v1") -// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time, -// and revision identifier (usually a 12-byte commit hash prefix). -func PseudoVersion(major, older string, t time.Time, rev string) string { - if major == "" { - major = "v0" - } - segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev) - build := semver.Build(older) - older = semver.Canonical(older) - if older == "" { - return major + ".0.0-" + segment // form (1) - } - if semver.Prerelease(older) != "" { - return older + ".0." + segment + build // form (4), (5) - } - - // Form (2), (3). - // Extract patch from vMAJOR.MINOR.PATCH - i := strings.LastIndex(older, ".") + 1 - v, patch := older[:i], older[i:] - - // Reassemble. - return v + incDecimal(patch) + "-0." + segment + build -} - -// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and -// revision, which may be used as a placeholder. -func ZeroPseudoVersion(major string) string { - return PseudoVersion(major, "", time.Time{}, "000000000000") -} - -// incDecimal returns the decimal string incremented by 1. -func incDecimal(decimal string) string { - // Scan right to left turning 9s to 0s until you find a digit to increment. - digits := []byte(decimal) - i := len(digits) - 1 - for ; i >= 0 && digits[i] == '9'; i-- { - digits[i] = '0' - } - if i >= 0 { - digits[i]++ - } else { - // digits is all zeros - digits[0] = '1' - digits = append(digits, '0') - } - return string(digits) -} - -// decDecimal returns the decimal string decremented by 1, or the empty string -// if the decimal is all zeroes. -func decDecimal(decimal string) string { - // Scan right to left turning 0s to 9s until you find a digit to decrement. - digits := []byte(decimal) - i := len(digits) - 1 - for ; i >= 0 && digits[i] == '0'; i-- { - digits[i] = '9' - } - if i < 0 { - // decimal is all zeros - return "" - } - if i == 0 && digits[i] == '1' && len(digits) > 1 { - digits = digits[1:] - } else { - digits[i]-- - } - return string(digits) -} - -// IsPseudoVersion reports whether v is a pseudo-version. -func IsPseudoVersion(v string) bool { - return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v) -} - -// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base, -// timestamp, and revision, as returned by [ZeroPseudoVersion]. -func IsZeroPseudoVersion(v string) bool { - return v == ZeroPseudoVersion(semver.Major(v)) -} - -// PseudoVersionTime returns the time stamp of the pseudo-version v. -// It returns an error if v is not a pseudo-version or if the time stamp -// embedded in the pseudo-version is not a valid time. -func PseudoVersionTime(v string) (time.Time, error) { - _, timestamp, _, _, err := parsePseudoVersion(v) - if err != nil { - return time.Time{}, err - } - t, err := time.Parse("20060102150405", timestamp) - if err != nil { - return time.Time{}, &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("malformed time %q", timestamp), - } - } - return t, nil -} - -// PseudoVersionRev returns the revision identifier of the pseudo-version v. -// It returns an error if v is not a pseudo-version. -func PseudoVersionRev(v string) (rev string, err error) { - _, _, rev, _, err = parsePseudoVersion(v) - return -} - -// PseudoVersionBase returns the canonical parent version, if any, upon which -// the pseudo-version v is based. -// -// If v has no parent version (that is, if it is "vX.0.0-[…]"), -// PseudoVersionBase returns the empty string and a nil error. -func PseudoVersionBase(v string) (string, error) { - base, _, _, build, err := parsePseudoVersion(v) - if err != nil { - return "", err - } - - switch pre := semver.Prerelease(base); pre { - case "": - // vX.0.0-yyyymmddhhmmss-abcdef123456 → "" - if build != "" { - // Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible - // are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag, - // but the "+incompatible" suffix implies that the major version of - // the parent tag is not compatible with the module's import path. - // - // There are a few such entries in the index generated by proxy.golang.org, - // but we believe those entries were generated by the proxy itself. - return "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("lacks base version, but has build metadata %q", build), - } - } - return "", nil - - case "-0": - // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z - // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible - base = strings.TrimSuffix(base, pre) - i := strings.LastIndexByte(base, '.') - if i < 0 { - panic("base from parsePseudoVersion missing patch number: " + base) - } - patch := decDecimal(base[i+1:]) - if patch == "" { - // vX.0.0-0 is invalid, but has been observed in the wild in the index - // generated by requests to proxy.golang.org. - // - // NOTE(bcmills): I cannot find a historical bug that accounts for - // pseudo-versions of this form, nor have I seen such versions in any - // actual go.mod files. If we find actual examples of this form and a - // reasonable theory of how they came into existence, it seems fine to - // treat them as equivalent to vX.0.0 (especially since the invalid - // pseudo-versions have lower precedence than the real ones). For now, we - // reject them. - return "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("version before %s would have negative patch number", base), - } - } - return base[:i+1] + patch + build, nil - - default: - // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre - // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible - if !strings.HasSuffix(base, ".0") { - panic(`base from parsePseudoVersion missing ".0" before date: ` + base) - } - return strings.TrimSuffix(base, ".0") + build, nil - } -} - -var errPseudoSyntax = errors.New("syntax error") - -func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) { - if !IsPseudoVersion(v) { - return "", "", "", "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: errPseudoSyntax, - } - } - build = semver.Build(v) - v = strings.TrimSuffix(v, build) - j := strings.LastIndex(v, "-") - v, rev = v[:j], v[j+1:] - i := strings.LastIndex(v, "-") - if j := strings.LastIndex(v, "."); j > i { - base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0" - timestamp = v[j+1:] - } else { - base = v[:i] // "vX.0.0" - timestamp = v[i+1:] - } - return base, timestamp, rev, build, nil -} diff --git a/vendor/golang.org/x/mod/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go deleted file mode 100644 index 9a2dfd3..0000000 --- a/vendor/golang.org/x/mod/semver/semver.go +++ /dev/null @@ -1,401 +0,0 @@ -// 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 semver implements comparison of semantic version strings. -// In this package, semantic version strings must begin with a leading "v", -// as in "v1.0.0". -// -// The general form of a semantic version string accepted by this package is -// -// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] -// -// where square brackets indicate optional parts of the syntax; -// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; -// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers -// using only alphanumeric characters and hyphens; and -// all-numeric PRERELEASE identifiers must not have leading zeros. -// -// This package follows Semantic Versioning 2.0.0 (see semver.org) -// with two exceptions. First, it requires the "v" prefix. Second, it recognizes -// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) -// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. -package semver - -import "sort" - -// parsed returns the parsed form of a semantic version string. -type parsed struct { - major string - minor string - patch string - short string - prerelease string - build string -} - -// IsValid reports whether v is a valid semantic version string. -func IsValid(v string) bool { - _, ok := parse(v) - return ok -} - -// Canonical returns the canonical formatting of the semantic version v. -// It fills in any missing .MINOR or .PATCH and discards build metadata. -// Two semantic versions compare equal only if their canonical formattings -// are identical strings. -// The canonical invalid semantic version is the empty string. -func Canonical(v string) string { - p, ok := parse(v) - if !ok { - return "" - } - if p.build != "" { - return v[:len(v)-len(p.build)] - } - if p.short != "" { - return v + p.short - } - return v -} - -// Major returns the major version prefix of the semantic version v. -// For example, Major("v2.1.0") == "v2". -// If v is an invalid semantic version string, Major returns the empty string. -func Major(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return v[:1+len(pv.major)] -} - -// MajorMinor returns the major.minor version prefix of the semantic version v. -// For example, MajorMinor("v2.1.0") == "v2.1". -// If v is an invalid semantic version string, MajorMinor returns the empty string. -func MajorMinor(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - i := 1 + len(pv.major) - if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { - return v[:j] - } - return v[:i] + "." + pv.minor -} - -// Prerelease returns the prerelease suffix of the semantic version v. -// For example, Prerelease("v2.1.0-pre+meta") == "-pre". -// If v is an invalid semantic version string, Prerelease returns the empty string. -func Prerelease(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return pv.prerelease -} - -// Build returns the build suffix of the semantic version v. -// For example, Build("v2.1.0+meta") == "+meta". -// If v is an invalid semantic version string, Build returns the empty string. -func Build(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return pv.build -} - -// Compare returns an integer comparing two versions according to -// semantic version precedence. -// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. -// -// An invalid semantic version string is considered less than a valid one. -// All invalid semantic version strings compare equal to each other. -func Compare(v, w string) int { - pv, ok1 := parse(v) - pw, ok2 := parse(w) - if !ok1 && !ok2 { - return 0 - } - if !ok1 { - return -1 - } - if !ok2 { - return +1 - } - if c := compareInt(pv.major, pw.major); c != 0 { - return c - } - if c := compareInt(pv.minor, pw.minor); c != 0 { - return c - } - if c := compareInt(pv.patch, pw.patch); c != 0 { - return c - } - return comparePrerelease(pv.prerelease, pw.prerelease) -} - -// Max canonicalizes its arguments and then returns the version string -// that compares greater. -// -// Deprecated: use [Compare] instead. In most cases, returning a canonicalized -// version is not expected or desired. -func Max(v, w string) string { - v = Canonical(v) - w = Canonical(w) - if Compare(v, w) > 0 { - return v - } - return w -} - -// ByVersion implements [sort.Interface] for sorting semantic version strings. -type ByVersion []string - -func (vs ByVersion) Len() int { return len(vs) } -func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } -func (vs ByVersion) Less(i, j int) bool { - cmp := Compare(vs[i], vs[j]) - if cmp != 0 { - return cmp < 0 - } - return vs[i] < vs[j] -} - -// Sort sorts a list of semantic version strings using [ByVersion]. -func Sort(list []string) { - sort.Sort(ByVersion(list)) -} - -func parse(v string) (p parsed, ok bool) { - if v == "" || v[0] != 'v' { - return - } - p.major, v, ok = parseInt(v[1:]) - if !ok { - return - } - if v == "" { - p.minor = "0" - p.patch = "0" - p.short = ".0.0" - return - } - if v[0] != '.' { - ok = false - return - } - p.minor, v, ok = parseInt(v[1:]) - if !ok { - return - } - if v == "" { - p.patch = "0" - p.short = ".0" - return - } - if v[0] != '.' { - ok = false - return - } - p.patch, v, ok = parseInt(v[1:]) - if !ok { - return - } - if len(v) > 0 && v[0] == '-' { - p.prerelease, v, ok = parsePrerelease(v) - if !ok { - return - } - } - if len(v) > 0 && v[0] == '+' { - p.build, v, ok = parseBuild(v) - if !ok { - return - } - } - if v != "" { - ok = false - return - } - ok = true - return -} - -func parseInt(v string) (t, rest string, ok bool) { - if v == "" { - return - } - if v[0] < '0' || '9' < v[0] { - return - } - i := 1 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - if v[0] == '0' && i != 1 { - return - } - return v[:i], v[i:], true -} - -func parsePrerelease(v string) (t, rest string, ok bool) { - // "A pre-release version MAY be denoted by appending a hyphen and - // a series of dot separated identifiers immediately following the patch version. - // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. - // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." - if v == "" || v[0] != '-' { - return - } - i := 1 - start := 1 - for i < len(v) && v[i] != '+' { - if !isIdentChar(v[i]) && v[i] != '.' { - return - } - if v[i] == '.' { - if start == i || isBadNum(v[start:i]) { - return - } - start = i + 1 - } - i++ - } - if start == i || isBadNum(v[start:i]) { - return - } - return v[:i], v[i:], true -} - -func parseBuild(v string) (t, rest string, ok bool) { - if v == "" || v[0] != '+' { - return - } - i := 1 - start := 1 - for i < len(v) { - if !isIdentChar(v[i]) && v[i] != '.' { - return - } - if v[i] == '.' { - if start == i { - return - } - start = i + 1 - } - i++ - } - if start == i { - return - } - return v[:i], v[i:], true -} - -func isIdentChar(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' -} - -func isBadNum(v string) bool { - i := 0 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - return i == len(v) && i > 1 && v[0] == '0' -} - -func isNum(v string) bool { - i := 0 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - return i == len(v) -} - -func compareInt(x, y string) int { - if x == y { - return 0 - } - if len(x) < len(y) { - return -1 - } - if len(x) > len(y) { - return +1 - } - if x < y { - return -1 - } else { - return +1 - } -} - -func comparePrerelease(x, y string) int { - // "When major, minor, and patch are equal, a pre-release version has - // lower precedence than a normal version. - // Example: 1.0.0-alpha < 1.0.0. - // Precedence for two pre-release versions with the same major, minor, - // and patch version MUST be determined by comparing each dot separated - // identifier from left to right until a difference is found as follows: - // identifiers consisting of only digits are compared numerically and - // identifiers with letters or hyphens are compared lexically in ASCII - // sort order. Numeric identifiers always have lower precedence than - // non-numeric identifiers. A larger set of pre-release fields has a - // higher precedence than a smaller set, if all of the preceding - // identifiers are equal. - // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < - // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." - if x == y { - return 0 - } - if x == "" { - return +1 - } - if y == "" { - return -1 - } - for x != "" && y != "" { - x = x[1:] // skip - or . - y = y[1:] // skip - or . - var dx, dy string - dx, x = nextIdent(x) - dy, y = nextIdent(y) - if dx != dy { - ix := isNum(dx) - iy := isNum(dy) - if ix != iy { - if ix { - return -1 - } else { - return +1 - } - } - if ix { - if len(dx) < len(dy) { - return -1 - } - if len(dx) > len(dy) { - return +1 - } - } - if dx < dy { - return -1 - } else { - return +1 - } - } - } - if x == "" { - return -1 - } else { - return +1 - } -} - -func nextIdent(x string) (dx, rest string) { - i := 0 - for i < len(x) && x[i] != '.' { - i++ - } - return x[:i], x[i:] -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 332ae8f..108673f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,8 +1,3 @@ -# github.com/grafana/k6foundry v0.3.0 -## explicit; go 1.22.2 -github.com/grafana/k6foundry -# golang.org/x/mod v0.21.0 -## explicit; go 1.22.0 -golang.org/x/mod/internal/lazyregexp -golang.org/x/mod/module -golang.org/x/mod/semver +# github.com/Masterminds/semver/v3 v3.1.1 +## explicit +github.com/Masterminds/semver/v3