Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pip support #180

Merged
merged 27 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fc17906
Add rudimentary requirements.txt analysis
blast-hardcheese Dec 13, 2023
8225243
Start surfacing flags from ListRequirementsTxt
blast-hardcheese Dec 13, 2023
b6f1bae
Adding package removal to requirements.go
blast-hardcheese Dec 15, 2023
a294154
Grab flags as we parse requirements.txt
blast-hardcheese Dec 19, 2023
a701161
Also reflect package extras in the parser
blast-hardcheese Dec 19, 2023
32451e4
Accumulate "editable" package names as well
blast-hardcheese Dec 14, 2023
d65da6b
Expanding pep440 spec matcher to include multiple ranges
blast-hardcheese Dec 16, 2023
488c26d
Break out GuessRegexps
blast-hardcheese Dec 14, 2023
1898bf4
Break out searchPypi
blast-hardcheese Dec 19, 2023
aba9e64
Adding PythonPipBackend
blast-hardcheese Dec 19, 2023
227d671
Surfacing global flags from ListRequirementsText
blast-hardcheese Dec 13, 2023
d1d1cfd
upm add for pip
blast-hardcheese Dec 13, 2023
85defd2
Append new packages to requirements.txt
blast-hardcheese Dec 13, 2023
9700110
Remove packages from requirements.txt during upm remove
blast-hardcheese Dec 13, 2023
2cd1bd5
Look up requirements.txt in python_map
blast-hardcheese Dec 13, 2023
a879652
Bubble out util.Die from requirements.go
blast-hardcheese Dec 19, 2023
2292d43
Incorporate "o" to denote octal explicitly
blast-hardcheese Dec 14, 2023
852d15e
Adding pip test cases
blast-hardcheese Dec 15, 2023
5e72e41
Provide pip inside the dev environment
blast-hardcheese Dec 15, 2023
2f8ee8c
Aligning devshell python with test-suite
blast-hardcheese Dec 16, 2023
8fb095d
adding pip test cases
blast-hardcheese Dec 16, 2023
ab929f2
Define a virtualenv for python to permit pip to work at all
blast-hardcheese Dec 16, 2023
470fd76
Adding tests
blast-hardcheese Dec 19, 2023
109ce2c
Clarify _result
blast-hardcheese Dec 19, 2023
ba921c2
Remove anonymous blocks
blast-hardcheese Dec 19, 2023
31e152a
Break out cutPrefixes
blast-hardcheese Dec 19, 2023
8c3b123
No anonymous blocks.
blast-hardcheese Dec 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ endif
ifdef UPM_CI
test-suite:
go get gotest.tools/gotestsum
# TODO: Move the following setup out into before/after or a separate
# environment-specific runner
venv="$$(mktemp -d)"; \
python -m venv "$$venv"; \
source "$$venv/bin/activate"; \
cdmistman marked this conversation as resolved.
Show resolved Hide resolved
go run gotest.tools/gotestsum --junitfile ./junit.xml ./test-suite
else
test-suite:
Expand Down
1 change: 1 addition & 0 deletions internal/backends/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
// that comes first in this list will be used.
var languageBackends = []api.LanguageBackend{
python.PythonPoetryBackend,
python.PythonPipBackend,
nodejs.BunBackend,
nodejs.NodejsNPMBackend,
nodejs.NodejsPNPMBackend,
Expand Down
2 changes: 1 addition & 1 deletion internal/backends/backends_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestGetBackends(t *testing.T) {
for file, backend := range fileToBackend {
tmpfile := filepath.Join(dir, file)

if err := os.WriteFile(tmpfile, []byte{}, 0666); err != nil {
if err := os.WriteFile(tmpfile, []byte{}, 0o666); err != nil {
t.Errorf("failed to create empty file: %s err: %v", tmpfile, err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/backends/dart/dart.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func writeSpecFile(specs dartPubspecYaml) {
fmt.Println("Marshal Error")
}

err = os.WriteFile("pubspec.yaml", data, 0666)
err = os.WriteFile("pubspec.yaml", data, 0o666)
if err != nil {
panic(err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backends/nodejs/grab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ process.exit(1);

testDir := t.TempDir()
testFile := testDir + "/index.js"
err := os.WriteFile(testFile, []byte(content), 0644)
err := os.WriteFile(testFile, []byte(content), 0o644)
if err != nil {
t.Fatal("failed to write test file", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backends/python/gen_pypi_map/package_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func LoadPackageInfoFile(filePath string, cached *PackageInfo) error {
}

func SavePackageInfo(packageName string, cacheDir string, info *PackageInfo) error {
err := os.MkdirAll(cacheDir, 0774)
err := os.MkdirAll(cacheDir, 0o774)

if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion internal/backends/python/grab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import foo #upm package(bar)

testDir := t.TempDir()
testFile := testDir + "/index.py"
err := os.WriteFile(testFile, []byte(content), 0644)
err := os.WriteFile(testFile, []byte(content), 0o644)
if err != nil {
t.Fatal("failed to write test file", err)
}
Expand Down
187 changes: 169 additions & 18 deletions internal/backends/python/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ func add(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName stri
util.RunCmd(cmd)
}

func searchPypi(query string) []api.PkgInfo {
results, err := SearchPypi(query)
if err != nil {
util.Die("failed to search pypi: %s", err.Error())
}
return results
}

// makePythonPoetryBackend returns a backend for invoking poetry, given an arg0 for invoking Python
// (either a full path or just a name like "python3") to use when invoking Python.
func makePythonPoetryBackend(python string) api.LanguageBackend {
Expand Down Expand Up @@ -223,15 +231,9 @@ func makePythonPoetryBackend(python string) api.LanguageBackend {
},
SortPackages: pkg.SortPrefixSuffix(normalizePackageName),

Search: func(query string) []api.PkgInfo {
results, err := SearchPypi(query)
if err != nil {
util.Die("failed to search pypi: %s", err.Error())
}
return results
},
Info: info,
Add: add,
Search: searchPypi,
Info: info,
Add: add,
Remove: func(ctx context.Context, pkgs map[api.PkgName]bool) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "poetry remove")
Expand Down Expand Up @@ -280,15 +282,8 @@ func makePythonPoetryBackend(python string) api.LanguageBackend {
}
return pkgs
},
GuessRegexps: util.Regexps([]string{
// The (?:.|\\\n) subexpression allows us to
// match match multiple lines if
// backslash-escapes are used on the newlines.
`from (?:.|\\\n) import`,
`import ((?:.|\\\n)*) as`,
`import ((?:.|\\\n)*)`,
}),
Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { return guess(ctx, python) },
GuessRegexps: pythonGuessRegexps,
Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { return guess(ctx, python) },
InstallReplitNixSystemDependencies: func(ctx context.Context, pkgs []api.PkgName) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "python.InstallReplitNixSystemDependencies")
Expand All @@ -311,6 +306,161 @@ func makePythonPoetryBackend(python string) api.LanguageBackend {
}
}

var pythonGuessRegexps = util.Regexps([]string{
// The (?:.|\\\n) subexpression allows us to
// match match multiple lines if
// backslash-escapes are used on the newlines.
`from (?:.|\\\n) import`,
`import ((?:.|\\\n)*) as`,
`import ((?:.|\\\n)*)`,
})

// makePythonPipBackend returns a backend for invoking poetry, given an arg0 for invoking Python
// (either a full path or just a name like "python3") to use when invoking Python.
func makePythonPipBackend(python string) api.LanguageBackend {
var pipFlags []PipFlag
blast-hardcheese marked this conversation as resolved.
Show resolved Hide resolved

b := api.LanguageBackend{
Name: "python3-pip",
Specfile: "requirements.txt",
Alias: "python-python3-pip",
FilenamePatterns: []string{"*.py"},
Quirks: api.QuirksAddRemoveAlsoInstalls | api.QuirksNotReproducible,
NormalizePackageName: normalizePackageName,
GetPackageDir: func() string {
// Check if we're already inside an activated
// virtualenv. If so, just use it.
if venv := os.Getenv("VIRTUAL_ENV"); venv != "" {
return venv
}

if outputB, err := util.GetCmdOutputFallible([]string{
"python",
"-c", "import site; print(site.USER_SITE)",
}); err == nil {
return string(outputB)
}
cdmistman marked this conversation as resolved.
Show resolved Hide resolved

return ""
},
SortPackages: pkg.SortPrefixSuffix(normalizePackageName),

Search: searchPypi,
Info: info,
Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "pip install")
defer span.Finish()

cmd := []string{"pip", "install"}
for _, flag := range pipFlags {
cmd = append(cmd, string(flag))
}
for name, spec := range pkgs {
name := string(name)
spec := string(spec)

cmd = append(cmd, name+spec)
}
// Run install
util.RunCmd(cmd)
// Determine what was actually installed
{
blast-hardcheese marked this conversation as resolved.
Show resolved Hide resolved
outputB, err := util.GetCmdOutputFallible([]string{
"pip", "freeze",
})
cdmistman marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
util.Die("failed to run freeze: %s", err.Error())
}

var toAppend []string
for _, line := range strings.Split(string(outputB), "\n") {
var name api.PkgName
matches := matchPackageAndSpec.FindSubmatch(([]byte)(line))
if len(matches) > 0 {
name = normalizePackageName(api.PkgName(string(matches[1])))
}
if _, exists := pkgs[name]; exists {
toAppend = append(toAppend, line)
}
}

handle, err := os.OpenFile("requirements.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
util.Die("Unable to open requirements.txt for writing: %s", err)
}
defer handle.Close()
for _, line := range toAppend {
if _, err := handle.WriteString(line + "\n"); err != nil {
util.Die("Error writing to requirements.txt: %s", err)
}
}
}
},
Remove: func(ctx context.Context, pkgs map[api.PkgName]bool) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "pip uninstall")
defer span.Finish()

cmd := []string{"pip", "uninstall", "--yes"}
for name := range pkgs {
cmd = append(cmd, string(name))
}
util.RunCmd(cmd)
err := RemoveFromRequirementsTxt("requirements.txt", pkgs)
if err != nil {
util.Die("%s", err.Error())
}
},
Install: func(ctx context.Context) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "pip install")
defer span.Finish()

util.RunCmd([]string{"pip", "install", "-r", "requirements.txt"})
},
ListSpecfile: func() map[api.PkgName]api.PkgSpec {
flags, _result, err := ListRequirementsTxt("requirements.txt")
if err != nil {
util.Die("%s", err.Error())
}

result := make(map[api.PkgName]api.PkgSpec)
for name, spec := range _result {
blast-hardcheese marked this conversation as resolved.
Show resolved Hide resolved
result[normalizePackageName(name)] = spec
}

// Stash the seen flags into a module global
pipFlags = flags

return result
},
GuessRegexps: pythonGuessRegexps,
Guess: func(ctx context.Context) (map[api.PkgName]bool, bool) { return guess(ctx, python) },
InstallReplitNixSystemDependencies: func(ctx context.Context, pkgs []api.PkgName) {
//nolint:ineffassign,wastedassign,staticcheck
span, ctx := tracer.StartSpanFromContext(ctx, "python.InstallReplitNixSystemDependencies")
defer span.Finish()
ops := []nix.NixEditorOp{}
for _, pkg := range pkgs {
deps := nix.PythonNixDeps(string(pkg))
ops = append(ops, nix.ReplitNixAddToNixEditorOps(deps)...)
}

// Ignore the error here, because if we can't read the specfile,
// we still want to add the deps from above at least.
_, specfilePkgs, _ := ListRequirementsTxt("requirements.txt")
for pkg := range specfilePkgs {
deps := nix.PythonNixDeps(string(pkg))
ops = append(ops, nix.ReplitNixAddToNixEditorOps(deps)...)
}
nix.RunNixEditorOps(ops)
},
}

return b
}

func listPoetrySpecfile() (map[api.PkgName]api.PkgSpec, error) {
var cfg pyprojectTOML
if _, err := toml.DecodeFile("pyproject.toml", &cfg); err != nil {
Expand Down Expand Up @@ -360,3 +510,4 @@ func getPython3() string {

// PythonPoetryBackend is a UPM backend for Python 3 that uses Poetry.
var PythonPoetryBackend = makePythonPoetryBackend(getPython3())
var PythonPipBackend = makePythonPipBackend(getPython3())
Loading
Loading