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

feat: add consumer migration tool #226

Merged
merged 18 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions cmd/migrate/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/ipfs/boxo/cmd/migrate

go 1.19

require github.com/urfave/cli/v2 v2.25.1

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)
8 changes: 8 additions & 0 deletions cmd/migrate/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
114 changes: 114 additions & 0 deletions cmd/migrate/internal/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package migrate

import (
"encoding/json"
"fmt"
"io"
)

type Config struct {
ImportPaths map[string]string
Modules []string
}

var DefaultConfig = Config{
ImportPaths: map[string]string{
"github.com/ipfs/go-bitswap": "github.com/ipfs/boxo/bitswap",
"github.com/ipfs/go-ipfs-files": "github.com/ipfs/boxo/files",
"github.com/ipfs/tar-utils": "github.com/ipfs/boxo/tar",
"github.com/ipfs/interface-go-ipfs-core": "github.com/ipfs/boxo/coreiface",
"github.com/ipfs/go-unixfs": "github.com/ipfs/boxo/ipld/unixfs",
"github.com/ipfs/go-pinning-service-http-client": "github.com/ipfs/boxo/pinning/remote/client",
"github.com/ipfs/go-path": "github.com/ipfs/boxo/path",
"github.com/ipfs/go-namesys": "github.com/ipfs/boxo/namesys",
"github.com/ipfs/go-mfs": "github.com/ipfs/boxo/mfs",
"github.com/ipfs/go-ipfs-provider": "github.com/ipfs/boxo/provider",
"github.com/ipfs/go-ipfs-pinner": "github.com/ipfs/boxo/pinning/pinner",
"github.com/ipfs/go-ipfs-keystore": "github.com/ipfs/boxo/keystore",
"github.com/ipfs/go-filestore": "github.com/ipfs/boxo/filestore",
"github.com/ipfs/go-ipns": "github.com/ipfs/boxo/ipns",
"github.com/ipfs/go-blockservice": "github.com/ipfs/boxo/blockservice",
"github.com/ipfs/go-ipfs-chunker": "github.com/ipfs/boxo/chunker",
"github.com/ipfs/go-fetcher": "github.com/ipfs/boxo/fetcher",
"github.com/ipfs/go-ipfs-blockstore": "github.com/ipfs/boxo/blockstore",
"github.com/ipfs/go-ipfs-posinfo": "github.com/ipfs/boxo/filestore/posinfo",
"github.com/ipfs/go-ipfs-util": "github.com/ipfs/boxo/util",
"github.com/ipfs/go-ipfs-ds-help": "github.com/ipfs/boxo/datastore/dshelp",
"github.com/ipfs/go-verifcid": "github.com/ipfs/boxo/verifcid",
"github.com/ipfs/go-ipfs-exchange-offline": "github.com/ipfs/boxo/exchange/offline",
"github.com/ipfs/go-ipfs-routing": "github.com/ipfs/boxo/routing",
"github.com/ipfs/go-ipfs-exchange-interface": "github.com/ipfs/boxo/exchange",
"github.com/ipfs/go-merkledag": "github.com/ipfs/boxo/ipld/merkledag",
"github.com/ipld/go-car": "github.com/ipfs/boxo/ipld/car",

// Pre Boxo rename
"github.com/ipfs/go-libipfs/gateway": "github.com/ipfs/boxo/gateway",
"github.com/ipfs/go-libipfs/bitswap": "github.com/ipfs/boxo/bitswap",
"github.com/ipfs/go-libipfs/files": "github.com/ipfs/boxo/files",
"github.com/ipfs/go-libipfs/tar": "github.com/ipfs/boxo/tar",
"github.com/ipfs/go-libipfs/coreiface": "github.com/ipfs/boxo/coreiface",
"github.com/ipfs/go-libipfs/unixfs": "github.com/ipfs/boxo/ipld/unixfs",
"github.com/ipfs/go-libipfs/pinning/remote/client": "github.com/ipfs/boxo/pinning/remote/client",
"github.com/ipfs/go-libipfs/path": "github.com/ipfs/boxo/path",
"github.com/ipfs/go-libipfs/namesys": "github.com/ipfs/boxo/namesys",
"github.com/ipfs/go-libipfs/mfs": "github.com/ipfs/boxo/mfs",
"github.com/ipfs/go-libipfs/provider": "github.com/ipfs/boxo/provider",
"github.com/ipfs/go-libipfs/pinning/pinner": "github.com/ipfs/boxo/pinning/pinner",
"github.com/ipfs/go-libipfs/keystore": "github.com/ipfs/boxo/keystore",
"github.com/ipfs/go-libipfs/filestore": "github.com/ipfs/boxo/filestore",
"github.com/ipfs/go-libipfs/ipns": "github.com/ipfs/boxo/ipns",
"github.com/ipfs/go-libipfs/blockservice": "github.com/ipfs/boxo/blockservice",
"github.com/ipfs/go-libipfs/chunker": "github.com/ipfs/boxo/chunker",
"github.com/ipfs/go-libipfs/fetcher": "github.com/ipfs/boxo/fetcher",
"github.com/ipfs/go-libipfs/blockstore": "github.com/ipfs/boxo/blockstore",
"github.com/ipfs/go-libipfs/filestore/posinfo": "github.com/ipfs/boxo/filestore/posinfo",
"github.com/ipfs/go-libipfs/util": "github.com/ipfs/boxo/util",
"github.com/ipfs/go-libipfs/datastore/dshelp": "github.com/ipfs/boxo/datastore/dshelp",
"github.com/ipfs/go-libipfs/verifcid": "github.com/ipfs/boxo/verifcid",
"github.com/ipfs/go-libipfs/exchange/offline": "github.com/ipfs/boxo/exchange/offline",
"github.com/ipfs/go-libipfs/routing": "github.com/ipfs/boxo/routing",
"github.com/ipfs/go-libipfs/exchange": "github.com/ipfs/boxo/exchange",

// Unmigrated things
"github.com/ipfs/go-libipfs/blocks": "github.com/ipfs/go-block-format",
"github.com/ipfs/boxo/blocks": "github.com/ipfs/go-block-format",
},
Modules: []string{
"github.com/ipfs/go-bitswap",
"github.com/ipfs/go-ipfs-files",
"github.com/ipfs/tar-utils",
"gihtub.com/ipfs/go-block-format",
"github.com/ipfs/interface-go-ipfs-core",
"github.com/ipfs/go-unixfs",
"github.com/ipfs/go-pinning-service-http-client",
"github.com/ipfs/go-path",
"github.com/ipfs/go-namesys",
"github.com/ipfs/go-mfs",
"github.com/ipfs/go-ipfs-provider",
"github.com/ipfs/go-ipfs-pinner",
"github.com/ipfs/go-ipfs-keystore",
"github.com/ipfs/go-filestore",
"github.com/ipfs/go-ipns",
"github.com/ipfs/go-blockservice",
"github.com/ipfs/go-ipfs-chunker",
"github.com/ipfs/go-fetcher",
"github.com/ipfs/go-ipfs-blockstore",
"github.com/ipfs/go-ipfs-posinfo",
"github.com/ipfs/go-ipfs-util",
"github.com/ipfs/go-ipfs-ds-help",
"github.com/ipfs/go-verifcid",
"github.com/ipfs/go-ipfs-exchange-offline",
"github.com/ipfs/go-ipfs-routing",
"github.com/ipfs/go-ipfs-exchange-interface",
"github.com/ipfs/go-libipfs",
},
}

func ReadConfig(r io.Reader) (Config, error) {
var config Config
err := json.NewDecoder(r).Decode(&config)
if err != nil {
return Config{}, fmt.Errorf("reading and decoding config: %w", err)
}
return config, nil
}
23 changes: 23 additions & 0 deletions cmd/migrate/internal/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package migrate

import "path/filepath"

type pkgJSON struct {
Dir string
GoFiles []string
IgnoredGoFiles []string
TestGoFiles []string
XTestGoFiles []string
CgoFiles []string
}

func (p *pkgJSON) allSourceFiles() []string {
var files []string
lists := [...][]string{p.GoFiles, p.IgnoredGoFiles, p.TestGoFiles, p.CgoFiles, p.XTestGoFiles}
for _, l := range lists {
for _, f := range l {
files = append(files, filepath.Join(p.Dir, f))
}
}
return files
}
168 changes: 168 additions & 0 deletions cmd/migrate/internal/migrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package migrate

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"os"
"os/exec"
"strconv"
"strings"
)

type Migrator struct {
DryRun bool
Dir string
Config Config
}

func (m *Migrator) updateFileImports(filePath string) error {
fset := token.NewFileSet()
astFile, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("parsing %q: %w", filePath, err)
}

var fileChanged bool

var errr error
ast.Inspect(astFile, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.ImportSpec:
val, err := strconv.Unquote(x.Path.Value)
if err != nil {
errr = err
return false
}
// we take the first matching prefix, so you need to make sure you don't have ambiguous mappings
for from, to := range m.Config.ImportPaths {
if strings.HasPrefix(val, from) {
var newVal string
switch {
case len(val) == len(from):
newVal = to
case val[len(from)] != '/':
continue
default:
newVal = to + val[len(from):]
}
fmt.Printf("changing %s => %s in %s\n", x.Path.Value, newVal, filePath)
if !m.DryRun {
x.Path.Value = strconv.Quote(newVal)
fileChanged = true
}
}
}
}
return true
})
if errr != nil {
return errr
}

if !fileChanged {
return nil
}

f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
err = format.Node(f, fset, astFile)
if err != nil {
f.Close()
return fmt.Errorf("formatting %q: %w", filePath, err)
}
err = f.Close()
if err != nil {
return fmt.Errorf("closing %q: %w", filePath, err)
}

return nil
}

func (m *Migrator) run(cmdName string, args ...string) (int, string, string, error) {
cmd := exec.Command(cmdName, args...)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Dir = m.Dir
err := cmd.Start()
if err != nil {
return 0, "", "", fmt.Errorf("running %s %v: %w", cmdName, args, err)
}
state, err := cmd.Process.Wait()
if err != nil {
return 0, "", "", fmt.Errorf("waiting for %s %v: %w", cmdName, args, err)
}
return state.ExitCode(), strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), nil
}

// FindMigratedDependencies returns a list of dependent module versions like 'module v0.1.0' that have been migrated to go-libipfs.
func (m *Migrator) FindMigratedDependencies() ([]string, error) {
var modVersions []string
for _, mod := range m.Config.Modules {
exitCode, stdout, stderr, err := m.run("go", "list", "-m", mod)
if err != nil {
return nil, err
}
if exitCode == 0 {
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
modVersions = append(modVersions, scanner.Text())
}
} else {
if !strings.Contains(stderr, "not a known dependency") {
return nil, fmt.Errorf("non-zero exit code %d finding if current module depends on %q, stderr:\n%s", exitCode, mod, stderr)
}
}
}
return modVersions, nil
}

func (m *Migrator) findSourceFiles() ([]string, error) {
exitCode, stdout, stderr, err := m.run("go", "list", "-json", "./...")
if err != nil {
return nil, fmt.Errorf("finding source files: %w", err)
}
if exitCode != 0 {
return nil, fmt.Errorf("non-zero exit code %d finding source files, stderr:\n%s", exitCode, stderr)
}

var files []string
dec := json.NewDecoder(strings.NewReader(stdout))
for {
var pkg pkgJSON
err = dec.Decode(&pkg)
if err == io.EOF {
return files, nil
}
if err != nil {
return nil, fmt.Errorf("decoding 'go list' JSON: %w", err)
}
files = append(files, pkg.allSourceFiles()...)
}
}

// UpdateImports rewrites the imports of the current module for any import paths that have been migrated to go-libipfs.
func (m *Migrator) UpdateImports() error {
sourceFiles, err := m.findSourceFiles()
if err != nil {
return err
}
for _, sourceFile := range sourceFiles {
err := m.updateFileImports(sourceFile)
if err != nil {
return fmt.Errorf("updating imports in %q: %w", sourceFile, err)
}
}
return nil
}
Loading