diff --git a/internal/backends/python/python.go b/internal/backends/python/python.go index 5ef1e6db..beb1eddb 100644 --- a/internal/backends/python/python.go +++ b/internal/backends/python/python.go @@ -80,6 +80,15 @@ type poetryLock struct { } `json:"package"` } +func pep440Join(name api.PkgName, spec api.PkgSpec) string { + if matchSpecOnly.Match([]byte(spec)) { + return string(name) + string(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) +} + // normalizeSpec returns the version string from a Poetry spec, or the // empty string. The Poetry spec may be either a string or a // map[string]interface{} with a "version" key that is a string. If @@ -398,24 +407,18 @@ func makePythonPoetryBackend() api.LanguageBackend { cmd := []string{"poetry", "add"} for name, spec := range pkgs { - name := string(name) - if found, ok := moduleToPypiPackageAliases[name]; ok { + if found, ok := moduleToPypiPackageAliases[string(name)]; ok { delete(pkgs, api.PkgName(name)) - name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + name = api.PkgName(found) + pkgs[name] = api.PkgSpec(spec) } - spec := string(spec) // NB: this doesn't work if spec has // spaces in it, because of a bug in // Poetry that can't be worked around. // It looks like that bug might be // fixed in the 1.0 release though :/ - if spec != "" { - cmd = append(cmd, name+" "+spec) - } else { - cmd = append(cmd, name) - } + cmd = append(cmd, pep440Join(name, spec)) } util.RunCmd(cmd) }, @@ -523,15 +526,13 @@ func makePythonPipBackend() api.LanguageBackend { cmd = append(cmd, string(flag)) } for name, spec := range pkgs { - name := string(name) - spec := string(spec) - if found, ok := moduleToPypiPackageAliases[name]; ok { - delete(pkgs, api.PkgName(name)) - name = found - pkgs[api.PkgName(name)] = api.PkgSpec(spec) + if found, ok := moduleToPypiPackageAliases[string(name)]; ok { + delete(pkgs, name) + name = api.PkgName(found) + pkgs[name] = spec } - cmd = append(cmd, name+" "+spec) + cmd = append(cmd, pep440Join(name, spec)) } // Run install util.RunCmd(cmd) @@ -561,9 +562,7 @@ func makePythonPipBackend() api.LanguageBackend { 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 "===" - name := string(matches[1]) - userArgSpec := string(pkgs[rawName]) - toAppend = append(toAppend, name+userArgSpec) + toAppend = append(toAppend, pep440Join(name, pkgs[rawName])) } } } diff --git a/internal/backends/python/requirements.go b/internal/backends/python/requirements.go index 4ee24a76..5f53e924 100644 --- a/internal/backends/python/requirements.go +++ b/internal/backends/python/requirements.go @@ -20,6 +20,7 @@ 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])`)