Skip to content

Commit

Permalink
feat: port prune to rust (#5531)
Browse files Browse the repository at this point in the history
### Description

Port prune from Go to Rust and remove the old Go implementation. To
achieve this the following was done:
 - Parsing the lockfile during package graph construction
 - Porting utility functions
- Some minor changes in `turborepo_paths`, these were primarily moving
methods from their owned to borrowed counterparts
- Expanding `package.json` parsing to grab some of the information
pruning requires
- Porting of the prune command itself. I added some additional structure
compared to the Go version, but not enough that comparing it to the Go
version should be difficult

Notes for reviewers:
I apologize that this PR ended up touching as much as it did. Reviewing
the PR by commit should at least make all of the changes and their
impacts obvious. Commits before `2d4154c` are already on main and can be
skipped.

### Testing Instructions

Existing unit tests and integration tests for file copying and package
graph traversal. Actual lockfile behavior is mostly covered by unit
tests that were ported when the lockfile were ported.

Also did various manual testing with pruning monorepos.

---------

Co-authored-by: Chris Olszewski <Chris Olszewski>
  • Loading branch information
chris-olszewski committed Jul 19, 2023
1 parent 007b574 commit 396bf45
Show file tree
Hide file tree
Showing 45 changed files with 1,354 additions and 845 deletions.
13 changes: 12 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions cli/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/vercel/turbo/cli/internal/cmdutil"
"github.com/vercel/turbo/cli/internal/daemon"
"github.com/vercel/turbo/cli/internal/process"
"github.com/vercel/turbo/cli/internal/prune"
"github.com/vercel/turbo/cli/internal/run"
"github.com/vercel/turbo/cli/internal/signals"
"github.com/vercel/turbo/cli/internal/turbostate"
Expand Down Expand Up @@ -66,8 +65,6 @@ func RunWithExecutionState(executionState *turbostate.ExecutionState, turboVersi
command := executionState.CLIArgs.Command
if command.Daemon != nil {
execErr = daemon.ExecuteDaemon(ctx, helper, signalWatcher, executionState)
} else if command.Prune != nil {
execErr = prune.ExecutePrune(helper, executionState)
} else if command.Run != nil {
execErr = run.ExecuteRun(ctx, helper, signalWatcher, executionState)
} else {
Expand Down
50 changes: 0 additions & 50 deletions cli/internal/ffi/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,56 +225,6 @@ func toPackageManager(packageManager string) ffi_proto.PackageManager {
}
}

// Subgraph returns the contents of a lockfile subgraph
func Subgraph(packageManager string, content []byte, workspaces []string, packages []string, resolutions map[string]string) ([]byte, error) {
var additionalData *ffi_proto.AdditionalBerryData
if resolutions != nil {
additionalData = &ffi_proto.AdditionalBerryData{Resolutions: resolutions}
}
req := ffi_proto.SubgraphRequest{
Contents: content,
Workspaces: workspaces,
Packages: packages,
PackageManager: toPackageManager(packageManager),
Resolutions: additionalData,
}
reqBuf := Marshal(&req)
resBuf := C.subgraph(reqBuf)
reqBuf.Free()

resp := ffi_proto.SubgraphResponse{}
if err := Unmarshal(resBuf, resp.ProtoReflect().Interface()); err != nil {
panic(err)
}

if err := resp.GetError(); err != "" {
return nil, errors.New(err)
}

return resp.GetContents(), nil
}

// Patches returns all patch files referenced in the lockfile
func Patches(content []byte, packageManager string) []string {
req := ffi_proto.PatchesRequest{
Contents: content,
PackageManager: toPackageManager(packageManager),
}
reqBuf := Marshal(&req)
resBuf := C.patches(reqBuf)
reqBuf.Free()

resp := ffi_proto.PatchesResponse{}
if err := Unmarshal(resBuf, resp.ProtoReflect().Interface()); err != nil {
panic(err)
}
if err := resp.GetError(); err != "" {
panic(err)
}

return resp.GetPatches().GetPatches()
}

// RecursiveCopy copies src and its contents to dst
func RecursiveCopy(src string, dst string) error {
req := ffi_proto.RecursiveCopyRequest{
Expand Down
34 changes: 0 additions & 34 deletions cli/internal/lockfile/berry_lockfile.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package lockfile

import (
"io"

"github.com/vercel/turbo/cli/internal/ffi"
"github.com/vercel/turbo/cli/internal/turbopath"
)
Expand Down Expand Up @@ -31,38 +29,6 @@ func (l *BerryLockfile) AllDependencies(key string) (map[string]string, bool) {
panic("Should use Rust implementation")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (l *BerryLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
workspaces := make([]string, len(workspacePackages))
for i, workspace := range workspacePackages {
workspaces[i] = workspace.ToUnixPath().ToString()
}
contents, err := ffi.Subgraph("berry", l.contents, workspaces, packages, l.resolutions)
if err != nil {
return nil, err
}
return &BerryLockfile{contents: contents, resolutions: l.resolutions}, nil
}

// Encode encode the lockfile representation and write it to the given writer
func (l *BerryLockfile) Encode(w io.Writer) error {
_, err := w.Write(l.contents)
return err
}

// Patches return a list of patches used in the lockfile
func (l *BerryLockfile) Patches() []turbopath.AnchoredUnixPath {
rawPatches := ffi.Patches(l.contents, "berry")
if len(rawPatches) == 0 {
return nil
}
patches := make([]turbopath.AnchoredUnixPath, len(rawPatches))
for i, patch := range rawPatches {
patches[i] = turbopath.AnchoredUnixPath(patch)
}
return patches
}

// DecodeBerryLockfile Takes the contents of a berry lockfile and returns a struct representation
func DecodeBerryLockfile(contents []byte, resolutions map[string]string) (*BerryLockfile, error) {
return &BerryLockfile{contents: contents, resolutions: resolutions}, nil
Expand Down
16 changes: 0 additions & 16 deletions cli/internal/lockfile/berry_lockfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,6 @@ import (
"gotest.tools/v3/assert"
)

func Test_BerryPatches(t *testing.T) {
contents := getRustFixture(t, "berry.lock")
lf, err := DecodeBerryLockfile(contents, nil)
assert.NilError(t, err)
patches := lf.Patches()
assert.DeepEqual(t, patches, []turbopath.AnchoredUnixPath{".yarn/patches/lodash-npm-4.17.21-6382451519.patch"})
}

func Test_EmptyBerryPatches(t *testing.T) {
contents := getRustFixture(t, "minimal-berry.lock")
lf, err := DecodeBerryLockfile(contents, nil)
assert.NilError(t, err)
patches := lf.Patches()
assert.Assert(t, patches == nil)
}

func Test_BerryTransitiveClosure(t *testing.T) {
contents := getRustFixture(t, "berry.lock")
lf, err := DecodeBerryLockfile(contents, map[string]string{"lodash@^4.17.21": "patch:lodash@npm%3A4.17.21#./.yarn/patches/lodash-npm-4.17.21-6382451519.patch"})
Expand Down
7 changes: 0 additions & 7 deletions cli/internal/lockfile/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package lockfile

import (
"fmt"
"io"
"reflect"
"sort"

Expand All @@ -19,12 +18,6 @@ type Lockfile interface {
ResolvePackage(workspacePath turbopath.AnchoredUnixPath, name string, version string) (Package, error)
// AllDependencies Given a lockfile key return all (dev/optional/peer) dependencies of that package
AllDependencies(key string) (map[string]string, bool)
// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error)
// Encode encode the lockfile representation and write it to the given writer
Encode(w io.Writer) error
// Patches return a list of patches used in the lockfile
Patches() []turbopath.AnchoredUnixPath
// GlobalChange checks if there are any differences between lockfiles that would completely invalidate
// the cache.
GlobalChange(other Lockfile) bool
Expand Down
26 changes: 0 additions & 26 deletions cli/internal/lockfile/npm_lockfile.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package lockfile

import (
"io"

"github.com/vercel/turbo/cli/internal/ffi"
"github.com/vercel/turbo/cli/internal/turbopath"
)
Expand All @@ -27,30 +25,6 @@ func (l *NpmLockfile) AllDependencies(key string) (map[string]string, bool) {
panic("Unreachable")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (l *NpmLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
workspaces := make([]string, len(workspacePackages))
for i, workspace := range workspacePackages {
workspaces[i] = workspace.ToUnixPath().ToString()
}
contents, err := ffi.Subgraph("npm", l.contents, workspaces, packages, nil)
if err != nil {
return nil, err
}
return &NpmLockfile{contents: contents}, nil
}

// Encode the lockfile representation and write it to the given writer
func (l *NpmLockfile) Encode(w io.Writer) error {
_, err := w.Write(l.contents)
return err
}

// Patches return a list of patches used in the lockfile
func (l *NpmLockfile) Patches() []turbopath.AnchoredUnixPath {
return nil
}

// GlobalChange checks if there are any differences between lockfiles that would completely invalidate
// the cache.
func (l *NpmLockfile) GlobalChange(other Lockfile) bool {
Expand Down
34 changes: 0 additions & 34 deletions cli/internal/lockfile/pnpm_lockfile.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package lockfile

import (
"io"

"github.com/vercel/turbo/cli/internal/ffi"
"github.com/vercel/turbo/cli/internal/turbopath"
)
Expand Down Expand Up @@ -34,38 +32,6 @@ func (p *PnpmLockfile) AllDependencies(key string) (map[string]string, bool) {
panic("Unreachable")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (p *PnpmLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
workspaces := make([]string, len(workspacePackages))
for i, workspace := range workspacePackages {
workspaces[i] = workspace.ToUnixPath().ToString()
}
contents, err := ffi.Subgraph("pnpm", p.contents, workspaces, packages, nil)
if err != nil {
return nil, err
}
return &PnpmLockfile{contents: contents}, nil
}

// Encode encode the lockfile representation and write it to the given writer
func (p *PnpmLockfile) Encode(w io.Writer) error {
_, err := w.Write(p.contents)
return err
}

// Patches return a list of patches used in the lockfile
func (p *PnpmLockfile) Patches() []turbopath.AnchoredUnixPath {
rawPatches := ffi.Patches(p.contents, "pnpm")
if len(rawPatches) == 0 {
return nil
}
patches := make([]turbopath.AnchoredUnixPath, len(rawPatches))
for i, patch := range rawPatches {
patches[i] = turbopath.AnchoredUnixPath(patch)
}
return patches
}

// GlobalChange checks if there are any differences between lockfiles that would completely invalidate
// the cache.
func (p *PnpmLockfile) GlobalChange(other Lockfile) bool {
Expand Down
27 changes: 0 additions & 27 deletions cli/internal/lockfile/yarn_lockfile.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package lockfile

import (
"io"

"github.com/vercel/turbo/cli/internal/ffi"
"github.com/vercel/turbo/cli/internal/turbopath"
)

Expand All @@ -28,30 +25,6 @@ func (l *YarnLockfile) AllDependencies(key string) (map[string]string, bool) {
panic("Unreachable")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (l *YarnLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
workspaces := make([]string, len(workspacePackages))
for i, workspace := range workspacePackages {
workspaces[i] = workspace.ToUnixPath().ToString()
}
contents, err := ffi.Subgraph("yarn", l.contents, workspaces, packages, nil)
if err != nil {
return nil, err
}
return &YarnLockfile{contents: contents}, nil
}

// Encode encode the lockfile representation and write it to the given writer
func (l *YarnLockfile) Encode(w io.Writer) error {
_, err := w.Write(l.contents)
return err
}

// Patches return a list of patches used in the lockfile
func (l *YarnLockfile) Patches() []turbopath.AnchoredUnixPath {
return nil
}

// DecodeYarnLockfile Takes the contents of a yarn lockfile and returns a struct representation
func DecodeYarnLockfile(contents []byte) (*YarnLockfile, error) {
return &YarnLockfile{contents: contents}, nil
Expand Down
Loading

0 comments on commit 396bf45

Please sign in to comment.