From 09a4de8287baf4e3ab2e92c9951261b789da0a63 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 19 Dec 2024 17:59:55 -0800 Subject: [PATCH] [python] Fix bug parsing extras (#322) * Threading through PkgCoordinates instead of just PkgSpec into Add * Promoting pep440VersionComponent to a regexp * Promoting regexes to have proper "interfaces" with exposed indices * Demote extrasSpec to not group * Expose extrasSpec as an explicit top-level group * Promoting pep440Join to accept PkgCoordinates * Raise python "extras" to PkgCoordinates --- internal/api/types.go | 7 +- internal/backends/dart/dart.go | 8 +-- internal/backends/dotnet/dotnet.go | 2 +- internal/backends/dotnet/dotnet_cli.go | 8 +-- internal/backends/dotnet/dotnet_cli_test.go | 4 +- internal/backends/elisp/elisp.go | 8 +-- internal/backends/java/java.go | 5 +- internal/backends/nodejs/nodejs.go | 44 ++++++------ internal/backends/php/php.go | 8 +-- internal/backends/python/pypi_map.go | 4 +- internal/backends/python/python.go | 77 ++++++++++++--------- internal/backends/python/requirements.go | 29 ++++---- internal/backends/rlang/rlang.go | 6 +- internal/backends/ruby/ruby.go | 12 ++-- internal/backends/rust/rust.go | 8 +-- internal/cli/cmds.go | 10 +-- 16 files changed, 128 insertions(+), 112 deletions(-) diff --git a/internal/api/types.go b/internal/api/types.go index b3e19d64..52c38b3b 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -20,8 +20,9 @@ func (n PkgName) HasPrefix(other PkgName) bool { } type PkgCoordinates struct { - Name string - Spec PkgSpec + Name string + Spec PkgSpec + Extra any } // PkgSpec represents a package version constraint, e.g. "^1.1" or ">= @@ -263,7 +264,7 @@ type LanguageBackend struct { // it does not exist already. // // This field is mandatory. - Add func(context.Context, map[PkgName]PkgSpec, string) + Add func(context.Context, map[PkgName]PkgCoordinates, string) // Remove packages from the specfile. The map is guaranteed to // have at least one package, and all of the packages are diff --git a/internal/backends/dart/dart.go b/internal/backends/dart/dart.go index 2fd315cc..7ce07e1c 100644 --- a/internal/backends/dart/dart.go +++ b/internal/backends/dart/dart.go @@ -257,7 +257,7 @@ func readSpecFile() dartPubspecYaml { return specs } -func dartAdd(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { +func dartAdd(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "dartAdd") defer span.Finish() @@ -267,10 +267,10 @@ func dartAdd(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName specs := readSpecFile() - for name, spec := range pkgs { + for name, coords := range pkgs { arg := string(name) - if spec != "" { - specs.Dependencies[arg] = string(spec) + if coords.Spec != "" { + specs.Dependencies[arg] = string(coords.Spec) } else { specs.Dependencies[arg] = nil } diff --git a/internal/backends/dotnet/dotnet.go b/internal/backends/dotnet/dotnet.go index b4de458e..e166ed35 100644 --- a/internal/backends/dotnet/dotnet.go +++ b/internal/backends/dotnet/dotnet.go @@ -25,7 +25,7 @@ var DotNetBackend = api.LanguageBackend{ Remove: func(ctx context.Context, pkgs map[api.PkgName]bool) { removePackages(ctx, pkgs, findSpecFile(), util.RunCmd) }, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { addPackages(ctx, pkgs, projectName, util.RunCmd) }, Search: search, diff --git a/internal/backends/dotnet/dotnet_cli.go b/internal/backends/dotnet/dotnet_cli.go index abcf8af1..07c32363 100644 --- a/internal/backends/dotnet/dotnet_cli.go +++ b/internal/backends/dotnet/dotnet_cli.go @@ -19,14 +19,14 @@ func removePackages(ctx context.Context, pkgs map[api.PkgName]bool, specFileName } // adds packages using dotnet command which automatically updates lock files -func addPackages(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string, cmdRunner func([]string)) { +func addPackages(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string, cmdRunner func([]string)) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "dotnet add package") defer span.Finish() - for packageName, spec := range pkgs { + for packageName, coords := range pkgs { command := []string{"dotnet", "add", "package", string(packageName)} - if string(spec) != "" { - command = append(command, "--version", string(spec)) + if string(coords.Spec) != "" { + command = append(command, "--version", string(coords.Spec)) } cmdRunner(command) } diff --git a/internal/backends/dotnet/dotnet_cli_test.go b/internal/backends/dotnet/dotnet_cli_test.go index 3dcdbea0..58f53235 100644 --- a/internal/backends/dotnet/dotnet_cli_test.go +++ b/internal/backends/dotnet/dotnet_cli_test.go @@ -14,7 +14,7 @@ func TestAddPackages(t *testing.T) { cmds = append(cmds, strings.Join(cmd, " ")) } - addPackages(context.Background(), map[api.PkgName]api.PkgSpec{"package": "1.0"}, "", cmdRunner) + addPackages(context.Background(), map[api.PkgName]api.PkgCoordinates{"package": {Name: "package", Spec: "1.0"}}, "", cmdRunner) if len(cmds) != 1 { t.Errorf("Expected one command but got %q", len(cmds)) @@ -31,7 +31,7 @@ func TestAddPackagesWithoutVersion(t *testing.T) { cmds = append(cmds, strings.Join(cmd, " ")) } - addPackages(context.Background(), map[api.PkgName]api.PkgSpec{"package": ""}, "", cmdRunner) + addPackages(context.Background(), map[api.PkgName]api.PkgCoordinates{"package": {Name: "package"}}, "", cmdRunner) if len(cmds) != 1 { t.Errorf("Expected one command but got %q", len(cmds)) diff --git a/internal/backends/elisp/elisp.go b/internal/backends/elisp/elisp.go index 7984f6c8..3138a79f 100755 --- a/internal/backends/elisp/elisp.go +++ b/internal/backends/elisp/elisp.go @@ -87,7 +87,7 @@ var ElispBackend = api.LanguageBackend{ } return info }, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "elisp add") defer span.Finish() @@ -110,10 +110,10 @@ var ElispBackend = api.LanguageBackend{ contents += "\n" } - for name, spec := range pkgs { + for name, coords := range pkgs { contents += fmt.Sprintf(`(depends-on "%s"`, name) - if spec != "" { - contents += fmt.Sprintf(" %s", spec) + if coords.Spec != "" { + contents += fmt.Sprintf(" %s", coords.Spec) } contents += ")\n" } diff --git a/internal/backends/java/java.go b/internal/backends/java/java.go index 1e285326..bb42b756 100755 --- a/internal/backends/java/java.go +++ b/internal/backends/java/java.go @@ -121,7 +121,7 @@ func isAvailable() bool { return err == nil } -func addPackages(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { +func addPackages(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "Java add package") defer span.Finish() @@ -136,7 +136,8 @@ func addPackages(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectN } newDependencies := []Dependency{} - for pkgName, pkgSpec := range pkgs { + for pkgName, coords := range pkgs { + pkgSpec := coords.Spec submatches := pkgNameRegexp.FindStringSubmatch(string(pkgName)) if nil == submatches { util.DieConsistency( diff --git a/internal/backends/nodejs/nodejs.go b/internal/backends/nodejs/nodejs.go index 85ffd3e0..02ba7e13 100644 --- a/internal/backends/nodejs/nodejs.go +++ b/internal/backends/nodejs/nodejs.go @@ -411,7 +411,7 @@ var NodejsYarnBackend = api.LanguageBackend{ }, Search: nodejsSearch, Info: nodejsInfo, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "yarn (init) add") defer span.Finish() @@ -419,16 +419,17 @@ var NodejsYarnBackend = api.LanguageBackend{ util.RunCmd([]string{"yarn", "init", "-y"}) } cmd := []string{"yarn", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { name := string(name) if found, ok := moduleToYarnpkgPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + coords.Name = found + pkgs[api.PkgName(name)] = coords } arg := name - if spec != "" { - arg += "@" + string(spec) + if coords.Spec != "" { + arg += "@" + string(coords.Spec) } cmd = append(cmd, arg) } @@ -497,7 +498,7 @@ var NodejsPNPMBackend = api.LanguageBackend{ }, Search: nodejsSearch, Info: nodejsInfo, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "pnpm (init) add") defer span.Finish() @@ -505,16 +506,17 @@ var NodejsPNPMBackend = api.LanguageBackend{ util.RunCmd([]string{"pnpm", "init"}) } cmd := []string{"pnpm", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { name := string(name) if found, ok := moduleToNpmjsPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + coords.Name = found + pkgs[api.PkgName(name)] = coords } arg := name - if spec != "" { - arg += "@" + string(spec) + if coords.Spec != "" { + arg += "@" + string(coords.Spec) } cmd = append(cmd, arg) } @@ -600,7 +602,7 @@ var NodejsNPMBackend = api.LanguageBackend{ }, Search: nodejsSearch, Info: nodejsInfo, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "npm (init) install") defer span.Finish() @@ -608,16 +610,17 @@ var NodejsNPMBackend = api.LanguageBackend{ util.RunCmd([]string{"npm", "init", "-y"}) } cmd := []string{"npm", "install"} - for name, spec := range pkgs { + for name, coords := range pkgs { name := string(name) if found, ok := moduleToNpmjsPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + coords.Name = found + pkgs[api.PkgName(name)] = coords } arg := name - if spec != "" { - arg += "@" + string(spec) + if coords.Spec != "" { + arg += "@" + string(coords.Spec) } cmd = append(cmd, arg) } @@ -692,7 +695,7 @@ var BunBackend = api.LanguageBackend{ }, Search: nodejsSearch, Info: nodejsInfo, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "bun (init) add") defer span.Finish() @@ -701,16 +704,17 @@ var BunBackend = api.LanguageBackend{ util.RunCmd([]string{"bun", "init", "-y"}) } cmd := []string{"bun", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { name := string(name) if found, ok := moduleToNpmjsPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + coords.Name = found + pkgs[api.PkgName(name)] = coords } arg := name - if spec != "" { - arg += "@" + string(spec) + if coords.Spec != "" { + arg += "@" + string(coords.Spec) } cmd = append(cmd, arg) } diff --git a/internal/backends/php/php.go b/internal/backends/php/php.go index 3c9196e4..da2020e6 100644 --- a/internal/backends/php/php.go +++ b/internal/backends/php/php.go @@ -241,16 +241,16 @@ var PhpComposerBackend = api.LanguageBackend{ }, Search: search, Info: info, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectVendorName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectVendorName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "composer require") defer span.Finish() cmd := []string{"composer", "require"} - for name, spec := range pkgs { + for name, coords := range pkgs { arg := string(name) - if spec != "" { - arg += ":" + string(spec) + if coords.Spec != "" { + arg += ":" + string(coords.Spec) } cmd = append(cmd, arg) } diff --git a/internal/backends/python/pypi_map.go b/internal/backends/python/pypi_map.go index 263e3d5c..91d4f7ee 100644 --- a/internal/backends/python/pypi_map.go +++ b/internal/backends/python/pypi_map.go @@ -92,8 +92,8 @@ func (p *PypiMap) QueryToResults(query string) ([]api.PkgInfo, error) { return nil, err } packageNames = append(packageNames, api.PkgInfo{ - Name: packageName, - Version: version, + Name: packageName, + Version: version, Description: summary, }) } diff --git a/internal/backends/python/python.go b/internal/backends/python/python.go index 8746b46a..9800392c 100644 --- a/internal/backends/python/python.go +++ b/internal/backends/python/python.go @@ -112,15 +112,19 @@ type uvLock struct { } `toml:"package"` } -func pep440Join(name api.PkgName, spec api.PkgSpec) string { - if spec == "" { - return string(name) - } else if matchSpecOnly.Match([]byte(spec)) { - return string(name) + string(spec) +func pep440Join(coords api.PkgCoordinates) string { + var extra string + if _extra, ok := coords.Extra.(string); ok { + extra = _extra + } + if coords.Spec == "" { + return string(coords.Name) + extra + } else if MatchSpecOnly.Match([]byte(coords.Spec)) { + return string(coords.Name) + extra + string(coords.Spec) } // We did not match the version range separator in the spec, so we got // something like "foo 1.2.3", we need to return "foo==1.2.3" - return string(name) + "==" + string(spec) + return string(coords.Name) + extra + "==" + string(coords.Spec) } // normalizeSpec returns the version string from a Poetry spec, or the @@ -142,15 +146,16 @@ func normalizeSpec(spec interface{}) string { func normalizePackageArgs(args []string) map[api.PkgName]api.PkgCoordinates { pkgs := make(map[api.PkgName]api.PkgCoordinates) - versionComponent := regexp.MustCompile(pep440VersionComponent) for _, arg := range args { var rawName string var name api.PkgName + var extra string var spec api.PkgSpec - if found := matchPackageAndSpec.FindSubmatch([]byte(arg)); len(found) > 0 { - rawName = string(found[1]) + if found := MatchPackageAndSpec.FindSubmatch([]byte(arg)); len(found) > 0 { + rawName = string(found[MatchPackageAndSpecIndexName]) name = api.PkgName(rawName) - spec = api.PkgSpec(string(found[2])) + extra = string(found[MatchPackageAndSpecIndexExtras]) + spec = api.PkgSpec(string(found[MatchPackageAndSpecIndexVersion])) } else { split := strings.SplitN(arg, " ", 2) rawName = split[0] @@ -159,7 +164,7 @@ func normalizePackageArgs(args []string) map[api.PkgName]api.PkgCoordinates { specStr := strings.TrimSpace(split[1]) if specStr != "" { - if offset := versionComponent.FindIndex([]byte(spec)); len(offset) == 0 { + if offset := MatchPep440VersionComponent.FindIndex([]byte(spec)); len(offset) == 0 { spec = api.PkgSpec("==" + specStr) } else { spec = api.PkgSpec(specStr) @@ -170,8 +175,9 @@ func normalizePackageArgs(args []string) map[api.PkgName]api.PkgCoordinates { } } pkgs[normalizePackageName(name)] = api.PkgCoordinates{ - Name: rawName, - Spec: spec, + Name: rawName, + Spec: spec, + Extra: extra, } } return pkgs @@ -435,7 +441,7 @@ func makePythonPoetryBackend() api.LanguageBackend { Search: searchPypi, Info: info, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "poetry (init) add") defer span.Finish() @@ -451,11 +457,12 @@ func makePythonPoetryBackend() api.LanguageBackend { } cmd := []string{"poetry", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { if found, ok := moduleToPypiPackageAliases[string(name)]; ok { delete(pkgs, api.PkgName(name)) name = api.PkgName(found) - pkgs[name] = api.PkgSpec(spec) + coords.Name = found + pkgs[name] = coords } // NB: this doesn't work if spec has @@ -463,7 +470,7 @@ func makePythonPoetryBackend() api.LanguageBackend { // Poetry that can't be worked around. // It looks like that bug might be // fixed in the 1.0 release though :/ - cmd = append(cmd, pep440Join(name, spec)) + cmd = append(cmd, pep440Join(coords)) } util.RunCmd(cmd) }, @@ -569,7 +576,7 @@ func makePythonPipBackend() api.LanguageBackend { Search: searchPypi, Info: info, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "pip install") defer span.Finish() @@ -578,14 +585,15 @@ func makePythonPipBackend() api.LanguageBackend { for _, flag := range pipFlags { cmd = append(cmd, string(flag)) } - for name, spec := range pkgs { + for name, coords := range pkgs { if found, ok := moduleToPypiPackageAliases[string(name)]; ok { delete(pkgs, name) name = api.PkgName(found) - pkgs[name] = spec + coords.Name = found + pkgs[name] = coords } - cmd = append(cmd, pep440Join(name, spec)) + cmd = append(cmd, pep440Join(coords)) } // Run install util.RunCmd(cmd) @@ -609,13 +617,13 @@ func makePythonPipBackend() api.LanguageBackend { var toAppend []string for _, canonicalSpec := range strings.Split(string(outputB), "\n") { var name api.PkgName - matches := matchPackageAndSpec.FindSubmatch(([]byte)(canonicalSpec)) - if len(matches) > 0 { - name = normalizePackageName(api.PkgName(string(matches[1]))) + matches := MatchPackageAndSpec.FindSubmatch(([]byte)(canonicalSpec)) + if len(matches) >= MatchPackageAndSpecIndexName { + name = normalizePackageName(api.PkgName(string(matches[MatchPackageAndSpecIndexName]))) if rawName, ok := normalizedPkgs[name]; ok { // We've meticulously maintained the pkgspec from the CLI args, if specified, // so we don't clobber it with pip freeze's output of "===" - toAppend = append(toAppend, pep440Join(name, pkgs[rawName])) + toAppend = append(toAppend, pep440Join(pkgs[rawName])) } } } @@ -726,13 +734,13 @@ func makePythonUvBackend() api.LanguageBackend { var name *api.PkgName var spec *api.PkgSpec - matches := matchPackageAndSpec.FindSubmatch([]byte(dep)) - if len(matches) > 1 { - _name := api.PkgName(string(matches[1])) + matches := MatchPackageAndSpec.FindSubmatch([]byte(dep)) + if len(matches) >= MatchPackageAndSpecIndexName { + _name := api.PkgName(string(matches[MatchPackageAndSpecIndexName])) name = &_name } - if len(matches) > 2 { - _spec := api.PkgSpec(string(matches[2])) + if len(matches) >= MatchPackageAndSpecIndexVersion { + _spec := api.PkgSpec(string(matches[MatchPackageAndSpecIndexVersion])) spec = &_spec } else { _spec := api.PkgSpec("") @@ -790,7 +798,7 @@ func makePythonUvBackend() api.LanguageBackend { Search: searchPypi, Info: info, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "uv (init) add") defer span.Finish() @@ -816,14 +824,15 @@ func makePythonUvBackend() api.LanguageBackend { } cmd := []string{"uv", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { if found, ok := moduleToPypiPackageAliases[string(name)]; ok { delete(pkgs, name) name = api.PkgName(found) - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + coords.Name = found + pkgs[api.PkgName(name)] = coords } - cmd = append(cmd, pep440Join(name, spec)) + cmd = append(cmd, pep440Join(coords)) } util.RunCmd(cmd) }, diff --git a/internal/backends/python/requirements.go b/internal/backends/python/requirements.go index f04404e6..9a523e11 100644 --- a/internal/backends/python/requirements.go +++ b/internal/backends/python/requirements.go @@ -20,10 +20,15 @@ import ( var pep345Name = `(?:[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])` var pep440VersionComponent = `(?:(?:~=|!=|===|==|>=|<=|>|<)\s*[^, ]+)` var pep440VersionSpec = pep440VersionComponent + `(?:\s*,\s*` + pep440VersionComponent + `)*` -var matchSpecOnly = regexp.MustCompile(`^` + pep440VersionSpec + `$`) -var extrasSpec = `\[(` + pep345Name + `(?:\s*,\s*` + pep345Name + `)*)\]` -var matchPackageAndSpec = regexp.MustCompile(`(?i)^\s*(` + pep345Name + `)\s*` + `((?:` + extrasSpec + `)?\s*(?:` + pep440VersionSpec + `)?)?\s*$`) -var matchEggComponent = regexp.MustCompile(`(?i)\begg=(` + pep345Name + `)(?:$|[^A-Z0-9])`) +var extrasSpec = `\[` + pep345Name + `(?:\s*,\s*` + pep345Name + `)*\]` +var MatchEggComponent = regexp.MustCompile(`(?i)\begg=(` + pep345Name + `)(?:$|[^A-Z0-9])`) +var MatchEggComponentIndexEgg = 1 +var MatchPackageAndSpec = regexp.MustCompile(`(?i)^\s*(` + pep345Name + `)\s*(` + extrasSpec + `)?\s*(` + pep440VersionSpec + `)?\s*$`) +var MatchPackageAndSpecIndexName = 1 +var MatchPackageAndSpecIndexExtras = 2 +var MatchPackageAndSpecIndexVersion = 3 +var MatchPep440VersionComponent = regexp.MustCompile(pep440VersionComponent) +var MatchSpecOnly = regexp.MustCompile(`^` + pep440VersionSpec + `$`) // Global options: // @@ -57,14 +62,14 @@ func findPackage(line string) (*api.PkgName, *api.PkgSpec, bool) { var found bool - matches := matchPackageAndSpec.FindSubmatch([]byte(line)) - if len(matches) > 1 { - _name := api.PkgName(string(matches[1])) + matches := MatchPackageAndSpec.FindSubmatch([]byte(line)) + if len(matches) >= MatchPackageAndSpecIndexName { + _name := api.PkgName(string(matches[MatchPackageAndSpecIndexName])) name = &_name found = true } - if len(matches) > 2 { - _spec := api.PkgSpec(string(matches[2])) + if len(matches) >= MatchPackageAndSpecIndexVersion { + _spec := api.PkgSpec(string(matches[MatchPackageAndSpecIndexVersion])) spec = &_spec } return name, spec, found @@ -121,9 +126,9 @@ func recurseRequirementsTxt(depth int, path string, sofar map[api.PkgName]api.Pk // from inserting it into requirements.txt, which would then cause a conflict // on the next run. if parts[0] == "-e" || parts[0] == "--editable" { - matches := matchEggComponent.FindSubmatch([]byte(line)) - if len(matches) > 1 { - sofar[api.PkgName(string(matches[1]))] = "" + matches := MatchEggComponent.FindSubmatch([]byte(line)) + if len(matches) >= MatchEggComponentIndexEgg { + sofar[api.PkgName(string(matches[MatchEggComponentIndexEgg]))] = "" } } } diff --git a/internal/backends/rlang/rlang.go b/internal/backends/rlang/rlang.go index 8d8815de..8e75ead3 100644 --- a/internal/backends/rlang/rlang.go +++ b/internal/backends/rlang/rlang.go @@ -130,11 +130,11 @@ var RlangBackend = api.LanguageBackend{ return api.PkgInfo{} }, - Add: func(ctx context.Context, packages map[api.PkgName]api.PkgSpec, projectName string) { - for name, info := range packages { + Add: func(ctx context.Context, packages map[api.PkgName]api.PkgCoordinates, projectName string) { + for name, coords := range packages { RAdd(ctx, RPackage{ Name: string(name), - Version: string(info), + Version: string(coords.Spec), }) } }, diff --git a/internal/backends/ruby/ruby.go b/internal/backends/ruby/ruby.go index 1dc111e7..f90dcd19 100644 --- a/internal/backends/ruby/ruby.go +++ b/internal/backends/ruby/ruby.go @@ -177,7 +177,7 @@ var RubyBackend = api.LanguageBackend{ Dependencies: deps, } }, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "bundle (init) add") defer span.Finish() @@ -185,8 +185,8 @@ var RubyBackend = api.LanguageBackend{ util.RunCmd([]string{"bundle", "init"}) } args := []string{} - for name, spec := range pkgs { - if spec == "" { + for name, coords := range pkgs { + if coords.Spec == "" { args = append(args, string(name)) } } @@ -197,10 +197,10 @@ var RubyBackend = api.LanguageBackend{ util.RunCmd(append([]string{ "bundle", "add", "--skip-install"}, args...)) } - for name, spec := range pkgs { - if spec != "" { + for name, coords := range pkgs { + if coords.Spec != "" { nameArg := string(name) - versionArg := "--version=" + string(spec) + versionArg := "--version=" + string(coords.Spec) util.RunCmd([]string{"bundle", "add", nameArg, versionArg}) } } diff --git a/internal/backends/rust/rust.go b/internal/backends/rust/rust.go index a6b966b2..b2f1db2d 100644 --- a/internal/backends/rust/rust.go +++ b/internal/backends/rust/rust.go @@ -237,7 +237,7 @@ var RustBackend = api.LanguageBackend{ }, Search: search, Info: info, - Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) { + Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgCoordinates, projectName string) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "cargo add") defer span.Finish() @@ -245,10 +245,10 @@ var RustBackend = api.LanguageBackend{ util.RunCmd([]string{"cargo", "init", "."}) } cmd := []string{"cargo", "add"} - for name, spec := range pkgs { + for name, coords := range pkgs { arg := string(name) - if spec != "" { - arg += "@" + string(spec) + if coords.Spec != "" { + arg += "@" + string(coords.Spec) } cmd = append(cmd, arg) } diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index 22e3e578..b70d6d5f 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -338,7 +338,8 @@ func runAdd( if util.Exists(b.Specfile) { s := silenceSubroutines() for name, spec := range b.ListSpecfile(true) { - if spec == normPkgs[b.NormalizePackageName(name)].Spec { + coords := normPkgs[b.NormalizePackageName(name)] + if spec == coords.Spec && coords.Extra == nil { delete(normPkgs, b.NormalizePackageName(name)) } } @@ -350,12 +351,7 @@ func runAdd( } if len(normPkgs) >= 1 { - pkgs := map[api.PkgName]api.PkgSpec{} - for _, nameAndSpec := range normPkgs { - pkgs[api.PkgName(nameAndSpec.Name)] = nameAndSpec.Spec - } - - b.Add(ctx, pkgs, name) + b.Add(ctx, normPkgs, name) } if len(normPkgs) == 0 || b.QuirksDoesAddRemoveNotAlsoLock() {