From fb30f95cd8adbf3347d2ab57abacb73be595a357 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 07:40:08 -0800 Subject: [PATCH 01/10] Reflow Guess to be a map[string][]api.PkgName --- internal/api/types.go | 2 +- internal/backends/dart/dart.go | 2 +- internal/backends/elisp/elisp.go | 11 ++++---- internal/backends/nodejs/grab.go | 10 +++---- internal/backends/nodejs/nodejs_test.go | 28 +++++++++---------- internal/backends/php/php.go | 2 +- internal/backends/python/grab.go | 22 +++++++++++---- internal/backends/python/pypi_map.override.go | 22 +++++++-------- internal/backends/python/python.go | 6 ++-- internal/backends/rlang/rlang.go | 2 +- internal/backends/ruby/ruby.go | 4 +-- internal/backends/rust/rust.go | 2 +- internal/cli/cmds.go | 7 +++-- internal/store/store.go | 10 +++---- resources/ruby/guess-gems.rb | 2 +- 15 files changed, 72 insertions(+), 60 deletions(-) diff --git a/internal/api/types.go b/internal/api/types.go index 85de59dd..de36c2e1 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -328,7 +328,7 @@ type LanguageBackend struct { // (which is now wrong). // // This field is mandatory. - Guess func(ctx context.Context) (map[PkgName]bool, bool) + Guess func(ctx context.Context) (map[string][]PkgName, bool) // Installs system dependencies into replit.nix for supported // languages. diff --git a/internal/backends/dart/dart.go b/internal/backends/dart/dart.go index 6f456656..72dc287f 100644 --- a/internal/backends/dart/dart.go +++ b/internal/backends/dart/dart.go @@ -293,7 +293,7 @@ func dartRemove(ctx context.Context, pkgs map[api.PkgName]bool) { } // dartGuess stub. -func dartGuess(context.Context) (map[api.PkgName]bool, bool) { +func dartGuess(context.Context) (map[string][]api.PkgName, bool) { util.Die("Guess not implemented!") return nil, false diff --git a/internal/backends/elisp/elisp.go b/internal/backends/elisp/elisp.go index a14e98b2..3c494f0c 100755 --- a/internal/backends/elisp/elisp.go +++ b/internal/backends/elisp/elisp.go @@ -197,7 +197,7 @@ var ElispBackend = api.LanguageBackend{ GuessRegexps: util.Regexps([]string{ `\(\s*require\s*'\s*([^)[:space:]]+)[^)]*\)`, }), - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "elisp guess") defer span.Finish() @@ -210,7 +210,7 @@ var ElispBackend = api.LanguageBackend{ } if len(required) == 0 { - return map[api.PkgName]bool{}, true + return map[string][]api.PkgName{}, true } r = regexp.MustCompile( @@ -242,7 +242,7 @@ var ElispBackend = api.LanguageBackend{ clauses = append(clauses, fmt.Sprintf("feature = '%s'", feature)) } if len(clauses) == 0 { - return map[api.PkgName]bool{}, true + return map[string][]api.PkgName{}, true } where := strings.Join(clauses, " OR ") query := fmt.Sprintf("SELECT package FROM provided PR WHERE (%s) "+ @@ -255,9 +255,10 @@ var ElispBackend = api.LanguageBackend{ output := string(util.GetCmdOutput([]string{"sqlite3", epkgs, query})) r = regexp.MustCompile(`"(.+?)"`) - names := map[api.PkgName]bool{} + names := map[string][]api.PkgName{} for _, match := range r.FindAllStringSubmatch(output, -1) { - names[api.PkgName(match[1])] = true + name := match[1] + names[name] = []api.PkgName{api.PkgName(name)} } return names, true }, diff --git a/internal/backends/nodejs/grab.go b/internal/backends/nodejs/grab.go index 3d3dc81b..4b912d62 100644 --- a/internal/backends/nodejs/grab.go +++ b/internal/backends/nodejs/grab.go @@ -57,7 +57,7 @@ var internalModules = []string{ } // nodejsGuess implements Guess for nodejs-yarn, nodejs-pnpm and nodejs-npm. -func nodejsGuess(ctx context.Context) (map[api.PkgName]bool, bool) { +func nodejsGuess(ctx context.Context) (map[string][]api.PkgName, bool) { span, ctx := tracer.StartSpanFromContext(ctx, "nodejsGuess") defer span.Finish() cwd, err := os.Getwd() @@ -122,18 +122,18 @@ func findImports(ctx context.Context, dir string) (map[string]bool, error) { return foundImportPaths, nil } -func filterImports(ctx context.Context, foundPaths map[string]bool) map[api.PkgName]bool { +func filterImports(ctx context.Context, foundPaths map[string]bool) map[string][]api.PkgName { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "nodejs.grab.filterImports") defer span.Finish() - pkgs := map[api.PkgName]bool{} + pkgs := map[string][]api.PkgName{} for mod := range foundPaths { if mod == "" { continue } - if pkgs[api.PkgName(mod)] { + if _, ok := pkgs[mod]; ok { continue } @@ -195,7 +195,7 @@ func filterImports(ctx context.Context, foundPaths map[string]bool) map[api.PkgN } } - pkgs[api.PkgName(mod)] = true + pkgs[mod] = []api.PkgName{api.PkgName(mod)} } return pkgs diff --git a/internal/backends/nodejs/nodejs_test.go b/internal/backends/nodejs/nodejs_test.go index 85161a91..957bbb89 100644 --- a/internal/backends/nodejs/nodejs_test.go +++ b/internal/backends/nodejs/nodejs_test.go @@ -12,7 +12,7 @@ type TestCase struct { scenario string backend api.LanguageBackend fileContent string - expected map[api.PkgName]bool + expected map[string]bool } func TestNodejsYarnBackend_Guess(t *testing.T) { @@ -25,7 +25,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { export const App = () => null; `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "react": true, }, }, @@ -35,7 +35,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { fileContent: ` const request = require('request'); `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "request": true, }, }, @@ -45,7 +45,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { fileContent: ` const http = require('http'); `, - expected: map[api.PkgName]bool{}, + expected: map[string]bool{}, }, { scenario: "Ignore internal submodules imports", @@ -53,7 +53,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { fileContent: ` const dns = require('dns/promises'); `, - expected: map[api.PkgName]bool{}, + expected: map[string]bool{}, }, { scenario: "Returns both requires and imports in mixed file", @@ -62,7 +62,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { const request = require('request'); import yargs from 'yargs'; `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "request": true, "yargs": true, }, @@ -76,7 +76,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { export const App = () => React.createElement(SomeComponent, {}); `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "react": true, }, }, @@ -89,7 +89,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { export const App = () => React.createElement(SomeComponent, {}); `, - expected: map[api.PkgName]bool{}, + expected: map[string]bool{}, }, { scenario: "Will process packages in namespace", @@ -100,7 +100,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { export const App = () => React.createElement(Button, { color: "primary" }, "Hello, World!"); `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "react": true, "@material-ui/core": true, }, @@ -113,7 +113,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { require("node-fetch"); } `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "node-fetch": true, }, }, @@ -127,7 +127,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { require("node-fetch"); } `, - expected: map[api.PkgName]bool{}, + expected: map[string]bool{}, }, { scenario: "dynamic import", @@ -138,7 +138,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { import("node-fetch"); } `, - expected: map[api.PkgName]bool{ + expected: map[string]bool{ "node-fetch": true, }, }, @@ -152,7 +152,7 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { import("node-fetch"); } `, - expected: map[api.PkgName]bool{}, + expected: map[string]bool{}, }, } @@ -197,7 +197,7 @@ func verify(t *testing.T, tc TestCase, extension string) { } for key := range tc.expected { - if !result[key] { + if _, ok := result[key]; !ok { t.Errorf("Key %s not found in result map", key) } } diff --git a/internal/backends/php/php.go b/internal/backends/php/php.go index ffd3061d..d084d5b9 100644 --- a/internal/backends/php/php.go +++ b/internal/backends/php/php.go @@ -280,7 +280,7 @@ var PhpComposerBackend = api.LanguageBackend{ }, ListSpecfile: listSpecfile, ListLockfile: listLockfile, - Guess: func(context.Context) (map[api.PkgName]bool, bool) { + Guess: func(context.Context) (map[string][]api.PkgName, bool) { util.NotImplemented() return nil, false }, diff --git a/internal/backends/python/grab.go b/internal/backends/python/grab.go index 4125965d..9236b544 100644 --- a/internal/backends/python/grab.go +++ b/internal/backends/python/grab.go @@ -244,7 +244,7 @@ var internalModules = map[string]bool{ "zoneinfo": true, } -func guess(ctx context.Context, python string) (map[api.PkgName]bool, bool) { +func guess(ctx context.Context, python string) (map[string][]api.PkgName, bool) { span, ctx := tracer.StartSpanFromContext(ctx, "python.grab.guess") defer span.Finish() cwd, err := os.Getwd() @@ -278,7 +278,7 @@ func findImports(ctx context.Context, dir string) (map[string]bool, error) { return foundImportPaths, nil } -func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[api.PkgName]bool, bool) { +func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[string][]api.PkgName, bool) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "python.grab.filterImports") defer span.Finish() @@ -296,11 +296,12 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[api.PkgN } defer pypiMap.Close() - pkgs := map[api.PkgName]bool{} + pkgs := map[string][]api.PkgName{} for fullModname := range foundPkgs { // try and look it up in Pypi var pkg string + var overrides []string var ok bool modNameParts := strings.Split(fullModname, ".") @@ -308,7 +309,7 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[api.PkgN testModName := strings.Join(modNameParts, ".") // test overrides - pkg, ok = moduleToPypiPackageOverride[testModName] + overrides, ok = moduleToPypiPackageOverride[testModName] if ok { break } @@ -324,8 +325,17 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[api.PkgN } if ok { - name := api.PkgName(pkg) - pkgs[normalizePackageName(name)] = true + if pkg != "" { + name := api.PkgName(pkg) + pkgs[pkg] = []api.PkgName{normalizePackageName(name)} + } else { + group := []api.PkgName{} + for _, name := range overrides { + name := api.PkgName(name) + group = append(group, normalizePackageName(name)) + } + pkgs[overrides[0]] = group + } } } diff --git a/internal/backends/python/pypi_map.override.go b/internal/backends/python/pypi_map.override.go index e9544fc4..3d765134 100644 --- a/internal/backends/python/pypi_map.override.go +++ b/internal/backends/python/pypi_map.override.go @@ -3,20 +3,20 @@ package python /* Manual module -> package mapping overrides */ -var moduleToPypiPackageOverride = map[string]string{ - "grpc_status": "grpcio-status", // 2nd most popular - "nvd3": "python-nvd3", // not popular enough 6th in popularity - "requirements": "requirements-parser", // popular rlbot depends on it, but doesn't supply requires_dist - "base62": "pybase62", // it was overridden by base-62 which wins due to name match but is less popular by far - "faiss": "faiss-cpu", // faiss is offered as precompiled wheels, faiss-cpu and faiss-gpu. - "graphics": "graphics.py", // this package is popular, but the module doesn't match the package name https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/graphics.html#a-graphics-introduction - "replit.ai": "replit-ai", // Replit's AI package +var moduleToPypiPackageOverride = map[string][]string{ + "grpc_status": {"grpcio-status"}, // 2nd most popular + "nvd3": {"python-nvd3"}, // not popular enough 6th in popularity + "requirements": {"requirements-parser"}, // popular rlbot depends on it, but doesn't supply requires_dist + "base62": {"pybase62"}, // it was overridden by base-62 which wins due to name match but is less popular by far + "faiss": {"faiss-cpu"}, // faiss is offered as precompiled wheels, faiss-cpu and faiss-gpu. + "graphics": {"graphics.py"}, // this package is popular, but the module doesn't match the package name https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/graphics.html#a-graphics-introduction + "replit.ai": {"replit-ai"}, // Replit's AI package /* Proxy packages * * These are packages that provide helpful aliases, but otherwise provide no functionality. * We should prefer the real version. */ - "discord": "discord.py", - "bs4": "beautifulsoup4", - "glm": "PyGLM", + "discord": {"discord.py"}, + "bs4": {"beautifulsoup4"}, + "glm": {"PyGLM"}, } diff --git a/internal/backends/python/python.go b/internal/backends/python/python.go index cd892e7f..9bb654de 100644 --- a/internal/backends/python/python.go +++ b/internal/backends/python/python.go @@ -196,7 +196,7 @@ func add(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName stri func searchPypi(query string) []api.PkgInfo { if renamed, found := moduleToPypiPackageOverride[query]; found { - query = renamed + query = renamed[0] } results, err := SearchPypi(query) if err != nil { @@ -298,7 +298,7 @@ func makePythonPoetryBackend(python string) api.LanguageBackend { return pkgs }, GuessRegexps: pythonGuessRegexps, - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { return guess(ctx, python) }, + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { return guess(ctx, python) }, InstallReplitNixSystemDependencies: func(ctx context.Context, pkgs []api.PkgName) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "python.InstallReplitNixSystemDependencies") @@ -459,7 +459,7 @@ func makePythonPipBackend(python string) api.LanguageBackend { return pkgs }, GuessRegexps: pythonGuessRegexps, - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { return guess(ctx, python) }, + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { return guess(ctx, python) }, InstallReplitNixSystemDependencies: func(ctx context.Context, pkgs []api.PkgName) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "python.InstallReplitNixSystemDependencies") diff --git a/internal/backends/rlang/rlang.go b/internal/backends/rlang/rlang.go index a999cecd..a3d10752 100644 --- a/internal/backends/rlang/rlang.go +++ b/internal/backends/rlang/rlang.go @@ -176,7 +176,7 @@ var RlangBackend = api.LanguageBackend{ return pkgs }, //GuessRegexps: []*regexp.Regexp {regexp.MustCompile(`\brequire[ \t]*\(\s*([a-zA-Z_]\w*)\s*`)}, - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { util.NotImplemented() return nil, false diff --git a/internal/backends/ruby/ruby.go b/internal/backends/ruby/ruby.go index 126367d9..b4f2bfd6 100644 --- a/internal/backends/ruby/ruby.go +++ b/internal/backends/ruby/ruby.go @@ -255,14 +255,14 @@ var RubyBackend = api.LanguageBackend{ GuessRegexps: util.Regexps([]string{ `require\s*['"]([^'"]+)['"]`, }), - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "guess-gems.rb") defer span.Finish() guessedGems := util.GetCmdOutput([]string{ "ruby", "-e", util.GetResource("/ruby/guess-gems.rb"), }) - results := map[api.PkgName]bool{} + results := map[string][]api.PkgName{} if err := json.Unmarshal(guessedGems, &results); err != nil { util.Die("ruby: %s", err) } diff --git a/internal/backends/rust/rust.go b/internal/backends/rust/rust.go index 97168fa1..6773caf3 100644 --- a/internal/backends/rust/rust.go +++ b/internal/backends/rust/rust.go @@ -272,7 +272,7 @@ var RustBackend = api.LanguageBackend{ }, ListSpecfile: listSpecfile, ListLockfile: listLockfile, - Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { + Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) { util.NotImplemented() return nil, false }, diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index b2d2fc99..f78afb25 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -287,7 +287,8 @@ func runAdd( if guess { guessed := store.GuessWithCache(ctx, b, forceGuess) - for name := range guessed { + for _, guesses := range guessed { + name := guesses[0] if _, ok := normPkgs[b.NormalizePackageName(name)]; !ok { normPkgs[b.NormalizePackageName(name)] = pkgNameAndSpec{ name: name, @@ -535,8 +536,8 @@ func runGuess( // Map from normalized to original names. normPkgs := map[api.PkgName]api.PkgName{} - for pkg := range pkgs { - normPkgs[b.NormalizePackageName(pkg)] = pkg + for _, guesses := range pkgs { + normPkgs[b.NormalizePackageName(guesses[0])] = guesses[0] } if !all { diff --git a/internal/store/store.go b/internal/store/store.go index 04a22efe..7c945f04 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -148,7 +148,7 @@ func HasLockfileChanged(b api.LanguageBackend) bool { // backend does specify b.GuessRegexps, then the return value of this // function is cached.) If forceGuess is true, then write to but do // not read from the cache. -func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) map[api.PkgName]bool { +func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) map[string][]api.PkgName { span, ctx := tracer.StartSpanFromContext(ctx, "GuessWithCache") defer span.Finish() readMaybe() @@ -163,7 +163,7 @@ func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) cache.GuessedImportsHash = new } if forceGuess || new != old { - var pkgs map[api.PkgName]bool + var pkgs map[string][]api.PkgName success := true if new != "" { pkgs, success = b.Guess(ctx) @@ -174,7 +174,7 @@ func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) // case we shouldn't have any packages // returned by the bare imports search. Might // as well just skip the search, right? - pkgs = map[api.PkgName]bool{} + pkgs = map[string][]api.PkgName{} } if !success { // If bare imports search is not successful, @@ -200,9 +200,9 @@ func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) } return pkgs } else { - pkgs := map[api.PkgName]bool{} + pkgs := map[string][]api.PkgName{} for _, name := range cache.GuessedImports { - pkgs[api.PkgName(name)] = true + pkgs[name] = []api.PkgName{api.PkgName(name)} } return pkgs } diff --git a/resources/ruby/guess-gems.rb b/resources/ruby/guess-gems.rb index dd3dcb73..a9c233ab 100644 --- a/resources/ruby/guess-gems.rb +++ b/resources/ruby/guess-gems.rb @@ -47,7 +47,7 @@ def process_require(req_str, guesses) gem = $allowed_gems[req_str] if gem - guesses[gem] = true + guesses[gem] = [gem] end end From 6cde3b640fbd67314f74328e2b13b218d63fcbdf Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 07:55:52 -0800 Subject: [PATCH 02/10] Converting runGuess over to use full Guess pkg array --- internal/cli/cmds.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index f78afb25..09aaaf3c 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -532,29 +532,47 @@ func runGuess( span, ctx := trace.StartSpanFromExistingContext("runGuess") defer span.Finish() b := backends.GetBackend(ctx, language) - pkgs := store.GuessWithCache(ctx, b, forceGuess) + guessed := store.GuessWithCache(ctx, b, forceGuess) // Map from normalized to original names. - normPkgs := map[api.PkgName]api.PkgName{} - for _, guesses := range pkgs { - normPkgs[b.NormalizePackageName(guesses[0])] = guesses[0] + normPkgs := map[string][]api.PkgName{} + for key, guesses := range guessed { + normalized := []api.PkgName{} + for _, guess := range guesses { + normalized = append(normalized, b.NormalizePackageName(guess)) + } + normPkgs[key] = normalized } if !all { if util.Exists(b.Specfile) { for name := range b.ListSpecfile() { - delete(normPkgs, b.NormalizePackageName(name)) + name := b.NormalizePackageName(name) + for key, pkgs := range normPkgs { + for _, pkg := range pkgs { + if pkg == name { + delete(normPkgs, key) + } + } + } } } } - for _, pkg := range ignoredPackages { - delete(normPkgs, b.NormalizePackageName(api.PkgName(pkg))) + for _, ignored := range ignoredPackages { + ignored := b.NormalizePackageName(api.PkgName(ignored)) + for key, pkgs := range normPkgs { + for _, pkg := range pkgs { + if pkg == ignored { + delete(normPkgs, key) + } + } + } } lines := []string{} - for _, pkg := range normPkgs { - lines = append(lines, string(pkg)) + for _, pkgs := range normPkgs { + lines = append(lines, string(pkgs[0])) } sort.Strings(lines) From 986059247a7dccbff866d4a727ccd941b19b880e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 08:05:34 -0800 Subject: [PATCH 03/10] Converting runAdd over to use full Guess pkg array --- internal/cli/cmds.go | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index 09aaaf3c..0865356b 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -287,11 +287,39 @@ func runAdd( if guess { guessed := store.GuessWithCache(ctx, b, forceGuess) - for _, guesses := range guessed { - name := guesses[0] - if _, ok := normPkgs[b.NormalizePackageName(name)]; !ok { - normPkgs[b.NormalizePackageName(name)] = pkgNameAndSpec{ - name: name, + // Map from normalized package names to original + // names. + guessedNorm := map[string][]api.PkgName{} + for key, guesses := range guessed { + normalized := []api.PkgName{} + for _, guess := range guesses { + normalized = append(normalized, b.NormalizePackageName(guess)) + } + guessedNorm[key] = normalized + } + + for _, pkg := range ignoredPackages { + pkg := b.NormalizePackageName(api.PkgName(pkg)) + for key, guesses := range guessedNorm { + for _, guess := range guesses { + if pkg == guess { + delete(guessedNorm, key) + } + } + } + } + + for _, guesses := range guessedNorm { + found := false + for _, guess := range guesses { + if _, ok := normPkgs[guess]; !ok { + found = true + break + } + } + if !found { + normPkgs[b.NormalizePackageName(guesses[0])] = pkgNameAndSpec{ + name: guesses[0], spec: "", } } From 7f30645e5f9db322938d37f5678e180cd22735d4 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 10:16:34 -0800 Subject: [PATCH 04/10] Only suggest PyGLM if you do not have glm installed --- internal/backends/python/pypi_map.override.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backends/python/pypi_map.override.go b/internal/backends/python/pypi_map.override.go index 3d765134..592a5b4d 100644 --- a/internal/backends/python/pypi_map.override.go +++ b/internal/backends/python/pypi_map.override.go @@ -18,5 +18,5 @@ var moduleToPypiPackageOverride = map[string][]string{ */ "discord": {"discord.py"}, "bs4": {"beautifulsoup4"}, - "glm": {"PyGLM"}, + "glm": {"PyGLM", "glm"}, } From 2f28ca3d6fc7beccac3a998ed44ac0ff3a531e40 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 10:22:53 -0800 Subject: [PATCH 05/10] Lower-case fullModname before processing --- internal/backends/python/grab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backends/python/grab.go b/internal/backends/python/grab.go index 9236b544..d44603be 100644 --- a/internal/backends/python/grab.go +++ b/internal/backends/python/grab.go @@ -304,7 +304,7 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[string][ var overrides []string var ok bool - modNameParts := strings.Split(fullModname, ".") + modNameParts := strings.Split(strings.ToLower(fullModname), ".") for len(modNameParts) > 0 { testModName := strings.Join(modNameParts, ".") From f0a570de95be10ac8afb61b6590a19bdb9fb8307 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 28 Dec 2023 10:23:23 -0800 Subject: [PATCH 06/10] Adding PyGLM as a test for Guess renames --- test-suite/templates/guess/py/basic | 1 + test-suite/templates/guess/py/basic.expect | 1 + 2 files changed, 2 insertions(+) diff --git a/test-suite/templates/guess/py/basic b/test-suite/templates/guess/py/basic index 43cacaac..190157be 100644 --- a/test-suite/templates/guess/py/basic +++ b/test-suite/templates/guess/py/basic @@ -2,3 +2,4 @@ from django.shortcuts import render # this is an arbitrary comment from flask import Flask import replit.ai +import glm diff --git a/test-suite/templates/guess/py/basic.expect b/test-suite/templates/guess/py/basic.expect index a3bd0a23..e69db70d 100644 --- a/test-suite/templates/guess/py/basic.expect +++ b/test-suite/templates/guess/py/basic.expect @@ -1,3 +1,4 @@ django flask replit-ai +pyglm From 9755ebfd8256da5268ab32cf3abb2688a6eeda10 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Wed, 27 Dec 2023 17:21:00 -0800 Subject: [PATCH 07/10] Breaking out module aliases as distinct from import guessing - moduleToPypiPackageOverride is now exclusively for guessing and search overrides. - moduleToPypiPackageAliases is used only for guessing. Installing by way of search will then subsequently dereference the package in moduleToPypiPackageOverride, but without burdening the user's search experience. --- internal/backends/python/grab.go | 6 ++++++ internal/backends/python/pypi_map.override.go | 20 +++++++++++-------- internal/backends/python/python.go | 10 ++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/internal/backends/python/grab.go b/internal/backends/python/grab.go index d44603be..a3184254 100644 --- a/internal/backends/python/grab.go +++ b/internal/backends/python/grab.go @@ -314,6 +314,12 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[string][ break } + // test aliases + overrides, ok = moduleToPypiPackageAliases[testModName] + if ok { + break + } + // test pypi pkg, ok = pypiMap.ModuleToPackage(testModName) if ok { diff --git a/internal/backends/python/pypi_map.override.go b/internal/backends/python/pypi_map.override.go index 592a5b4d..380ce16b 100644 --- a/internal/backends/python/pypi_map.override.go +++ b/internal/backends/python/pypi_map.override.go @@ -11,12 +11,16 @@ var moduleToPypiPackageOverride = map[string][]string{ "faiss": {"faiss-cpu"}, // faiss is offered as precompiled wheels, faiss-cpu and faiss-gpu. "graphics": {"graphics.py"}, // this package is popular, but the module doesn't match the package name https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/graphics.html#a-graphics-introduction "replit.ai": {"replit-ai"}, // Replit's AI package - /* Proxy packages - * - * These are packages that provide helpful aliases, but otherwise provide no functionality. - * We should prefer the real version. - */ - "discord": {"discord.py"}, - "bs4": {"beautifulsoup4"}, - "glm": {"PyGLM", "glm"}, + "glm": {"PyGLM", "glm"}, // Both of these packages are valid, but PyGLM is a library, glm is an executable. +} + +/* Proxy packages + * + * These are packages that provide helpful aliases, but otherwise provide no functionality. + * We should prefer the real version. + */ +var moduleToPypiPackageAliases = map[string][]string{ + "bs4": {"beautifulsoup4"}, + "discord": {"discord.py"}, + "psycopg2": {"psycopg2-binary"}, // psycopg2 is a source package, psycopg2-binary is the dist wheel } diff --git a/internal/backends/python/python.go b/internal/backends/python/python.go index 9bb654de..5aeeecca 100644 --- a/internal/backends/python/python.go +++ b/internal/backends/python/python.go @@ -178,6 +178,11 @@ func add(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName stri cmd := []string{"poetry", "add"} for name, spec := range pkgs { name := string(name) + if found, ok := moduleToPypiPackageAliases[name]; ok { + delete(pkgs, api.PkgName(name)) + name = found[0] + pkgs[api.PkgName(name)] = api.PkgSpec(spec) + } spec := string(spec) // NB: this doesn't work if spec has @@ -375,6 +380,11 @@ func makePythonPipBackend(python string) api.LanguageBackend { for name, spec := range pkgs { name := string(name) spec := string(spec) + if found, ok := moduleToPypiPackageAliases[name]; ok { + delete(pkgs, api.PkgName(name)) + name = found[0] + pkgs[api.PkgName(name)] = api.PkgSpec(spec) + } cmd = append(cmd, name+spec) } From e6be8763ba501bd6eee58aa04159b5b07126592c Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Tue, 2 Jan 2024 14:02:49 -0800 Subject: [PATCH 08/10] pypi package aliases do not need to be a list --- internal/backends/python/grab.go | 2 +- internal/backends/python/pypi_map.override.go | 8 ++++---- internal/backends/python/python.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/backends/python/grab.go b/internal/backends/python/grab.go index a3184254..82185973 100644 --- a/internal/backends/python/grab.go +++ b/internal/backends/python/grab.go @@ -315,7 +315,7 @@ func filterImports(ctx context.Context, foundPkgs map[string]bool) (map[string][ } // test aliases - overrides, ok = moduleToPypiPackageAliases[testModName] + pkg, ok = moduleToPypiPackageAliases[testModName] if ok { break } diff --git a/internal/backends/python/pypi_map.override.go b/internal/backends/python/pypi_map.override.go index 380ce16b..2f30f7bd 100644 --- a/internal/backends/python/pypi_map.override.go +++ b/internal/backends/python/pypi_map.override.go @@ -19,8 +19,8 @@ var moduleToPypiPackageOverride = map[string][]string{ * These are packages that provide helpful aliases, but otherwise provide no functionality. * We should prefer the real version. */ -var moduleToPypiPackageAliases = map[string][]string{ - "bs4": {"beautifulsoup4"}, - "discord": {"discord.py"}, - "psycopg2": {"psycopg2-binary"}, // psycopg2 is a source package, psycopg2-binary is the dist wheel +var moduleToPypiPackageAliases = map[string]string{ + "bs4": "beautifulsoup4", + "discord": "discord.py", + "psycopg2": "psycopg2-binary", // psycopg2 is a source package, psycopg2-binary is the dist wheel } diff --git a/internal/backends/python/python.go b/internal/backends/python/python.go index 5aeeecca..498930d5 100644 --- a/internal/backends/python/python.go +++ b/internal/backends/python/python.go @@ -180,7 +180,7 @@ func add(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName stri name := string(name) if found, ok := moduleToPypiPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) - name = found[0] + name = found pkgs[api.PkgName(name)] = api.PkgSpec(spec) } spec := string(spec) @@ -382,7 +382,7 @@ func makePythonPipBackend(python string) api.LanguageBackend { spec := string(spec) if found, ok := moduleToPypiPackageAliases[name]; ok { delete(pkgs, api.PkgName(name)) - name = found[0] + name = found pkgs[api.PkgName(name)] = api.PkgSpec(spec) } From 1132e8c6f9548c7fda2dc8c506695be0be8b9534 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Tue, 2 Jan 2024 14:22:33 -0800 Subject: [PATCH 09/10] Clear guesses after add/remove --- internal/cli/cmds.go | 2 ++ internal/store/store.go | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index 0865356b..c6afd9bc 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -357,6 +357,7 @@ func runAdd( maybeInstall(ctx, b, forceInstall) } + store.ClearGuesses(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) } @@ -415,6 +416,7 @@ func runRemove(language string, args []string, upgrade bool, maybeInstall(ctx, b, forceInstall) } + store.ClearGuesses(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) } diff --git a/internal/store/store.go b/internal/store/store.go index 7c945f04..15363a4d 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -208,6 +208,16 @@ func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) } } +func ClearGuesses(ctx context.Context, b api.LanguageBackend) { + span, ctx := tracer.StartSpanFromContext(ctx, "ClearGuesses") + defer span.Finish() + + cache := getLanguageCache(b.Name, b.Alias) + + cache.GuessedImports = nil + cache.GuessedImportsHash = "" +} + // UpdateFileHashes caches the current states of the specfile and // lockfile. Neither file need exist. func UpdateFileHashes(ctx context.Context, b api.LanguageBackend) { From 37a81c085f72bddffedf1a64f133e05bbf25792e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Tue, 2 Jan 2024 16:54:03 -0800 Subject: [PATCH 10/10] UpdateFileHashes doesn't convey that it also initializes the cache --- internal/cli/cmds.go | 4 ++++ internal/store/store.go | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/internal/cli/cmds.go b/internal/cli/cmds.go index c6afd9bc..2566b97b 100644 --- a/internal/cli/cmds.go +++ b/internal/cli/cmds.go @@ -357,6 +357,7 @@ func runAdd( maybeInstall(ctx, b, forceInstall) } + store.Read(ctx, b) store.ClearGuesses(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) @@ -416,6 +417,7 @@ func runRemove(language string, args []string, upgrade bool, maybeInstall(ctx, b, forceInstall) } + store.Read(ctx, b) store.ClearGuesses(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) @@ -437,6 +439,7 @@ func runLock(language string, upgrade bool, forceLock bool, forceInstall bool) { maybeInstall(ctx, b, forceInstall) } + store.Read(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) } @@ -449,6 +452,7 @@ func runInstall(language string, force bool) { maybeInstall(ctx, b, force) + store.Read(ctx, b) store.UpdateFileHashes(ctx, b) store.Write(ctx) } diff --git a/internal/store/store.go b/internal/store/store.go index 15363a4d..c1cb16be 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -209,6 +209,7 @@ func GuessWithCache(ctx context.Context, b api.LanguageBackend, forceGuess bool) } func ClearGuesses(ctx context.Context, b api.LanguageBackend) { + //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "ClearGuesses") defer span.Finish() @@ -218,14 +219,20 @@ func ClearGuesses(ctx context.Context, b api.LanguageBackend) { cache.GuessedImportsHash = "" } +func Read(ctx context.Context, b api.LanguageBackend) { + //nolint:ineffassign,wastedassign,staticcheck + span, ctx := tracer.StartSpanFromContext(ctx, "store.Read") + defer span.Finish() + readMaybe() + initLanguage(b.Name, b.Alias) +} + // UpdateFileHashes caches the current states of the specfile and // lockfile. Neither file need exist. func UpdateFileHashes(ctx context.Context, b api.LanguageBackend) { //nolint:ineffassign,wastedassign,staticcheck span, ctx := tracer.StartSpanFromContext(ctx, "store.UpdateFileHashes") defer span.Finish() - readMaybe() - initLanguage(b.Name, b.Alias) cache := getLanguageCache(b.Name, b.Alias) cache.SpecfileHash = hashFile(b.Specfile) cache.LockfileHash = hashFile(b.Lockfile)