diff --git a/cmd/dylib-tree/main.go b/cmd/dylib-tree/main.go new file mode 100644 index 0000000..63429c3 --- /dev/null +++ b/cmd/dylib-tree/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "debug/macho" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/urfave/cli/v2" + "github.com/xlab/treeprint" +) + +var app = &cli.App{ + Name: "link-tree", + Usage: "recursive list shared-libraries as a tree", + UsageText: "link-tree [options] []", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "depth", + Usage: "max depth of tree (default: 0 = unlimited)", + Value: 0, + }, + &cli.StringSliceFlag{ + Name: "ignore", + Usage: "path patterns to ignore", + }, + + &cli.BoolFlag{ + Name: "ignore-system", + Usage: "ignore system libraries", + }, + &cli.BoolFlag{ + Name: "real-path", + Usage: "show resolved full paths instead of @executable_path " + + "and @rpath", + }, + }, + Action: actionHandler(LinkTreeCmd), +} + +type Context struct { + Root string + ExecutablePath string + Depth int + MaxDepth int + RealPath bool + Ignore []string + RPaths []string +} + +func (s *Context) WithFile(filename string) *Context { + ctx := *s + ctx.Root = filename + ctx.ExecutablePath = filepath.Dir(filename) + return &ctx +} + +func (s *Context) WithDepth(depth int) *Context { + ctx := *s + ctx.Depth = depth + return &ctx +} + +func (s *Context) WithIgnore(ignore []string) *Context { + ctx := *s + ctx.Ignore = append(ctx.Ignore, ignore...) + return &ctx +} + +func (s *Context) WithRpaths(rpaths []string) *Context { + ctx := *s + ctx.RPaths = append(ctx.RPaths, rpaths...) + return &ctx +} + +func actionHandler( + f func(*cli.Context, *Context) error, +) func(*cli.Context) error { + return func(c *cli.Context) error { + ignore := c.StringSlice("ignore") + if c.Bool("ignore-system") { + ignore = append( + ignore, + "/System/Library/*", + "*/libSystem.*.dylib", + "*/libobjc.*.dylib", + ) + } + + ctx := &Context{ + Ignore: ignore, + MaxDepth: c.Int("depth"), + RealPath: c.Bool("real-path"), + } + + return f(c, ctx) + } +} + +func LinkTreeCmd(c *cli.Context, ctx *Context) error { + for _, filename := range c.Args().Slice() { + ctx := ctx.WithFile(filename) + + treeRoot := treeprint.New() + tree := treeRoot.AddBranch(filename) + + err := processBinary(&tree, ctx, filename) + if err != nil { + return err + } + + fmt.Println(treeRoot.String()) + } + return nil +} + +func processBinary( + parent *treeprint.Tree, + ctx *Context, + filename string, +) error { + f, err := macho.Open(filename) + if err != nil { + return err + } + + ctx = ctx.WithDepth(ctx.Depth + 1).WithRpaths(getRpaths(f)) + + if ctx.MaxDepth > 0 && ctx.Depth > ctx.MaxDepth { + return nil + } + + libs, err := f.ImportedLibraries() + if err != nil { + return err + } + + for _, lib := range libs { + skip, err := ignoreLib(ctx, lib) + if err != nil { + return err + } + if skip { + continue + } + + filename, err := resolveLibFilename(ctx, lib) + if err != nil { + (*parent).AddBranch(lib) + continue + } + + skip, err = ignoreLib(ctx, filename) + if err != nil { + return err + } + if skip { + continue + } + + displayName := lib + if ctx.RealPath { + displayName = filename + } + + tree := (*parent).AddBranch(displayName) + + err = processBinary(&tree, ctx, filename) + if err != nil { + return err + } + } + + return nil +} + +func ignoreLib(ctx *Context, lib string) (bool, error) { + for _, pattern := range ctx.Ignore { + m, err := ignoreRegexp(pattern) + if err != nil { + return false, err + } + + if m.MatchString(lib) { + return true, nil + } + } + + return false, nil +} + +func ignoreRegexp(p string) (*regexp.Regexp, error) { + rp := "^" + regexp.QuoteMeta(p) + "$" + rp = strings.ReplaceAll(rp, `\*`, ".*") + rp = strings.ReplaceAll(rp, `\?`, ".") + + return regexp.Compile(rp) +} + +func resolveLibFilename(ctx *Context, lib string) (string, error) { + filename := lib + + if strings.HasPrefix(lib, "@executable_path") { + filename = filepath.Join(ctx.ExecutablePath, lib[16:]) + } else if strings.HasPrefix(lib, "@rpath") { + for _, r := range ctx.RPaths { + if strings.HasPrefix(r, "@executable_path") { + r = filepath.Join(ctx.ExecutablePath, r[16:]) + } + + rfile := filepath.Join(r, lib[6:]) + _, err := os.Stat(rfile) + if err != nil { + continue + } + + return rfile, nil + } + + return "", fmt.Errorf("could not find %s", lib) + } + + _, err := os.Stat(filename) + + return filename, err +} + +func getRpaths(f *macho.File) []string { + paths := []string{} + + for _, i := range f.Loads { + if r, ok := i.(*macho.Rpath); ok { + paths = append(paths, r.Path) + } + } + + return paths +} + +func main() { + err := app.Run(os.Args) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error()) + os.Exit(1) + } +} diff --git a/cmd/github-release/plan.go b/cmd/github-release/plan.go index 85a19a4..56acb31 100644 --- a/cmd/github-release/plan.go +++ b/cmd/github-release/plan.go @@ -6,8 +6,6 @@ import ( "gopkg.in/yaml.v3" ) -type ReleaseType string - type Release struct { Name string `yaml:"name"` Title string `yaml:"title,omitempty"` diff --git a/go.mod b/go.mod index c82821d..3fc83ee 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/google/go-github/v35 v35.1.0 github.com/urfave/cli/v2 v2.3.0 + github.com/xlab/treeprint v1.1.0 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 1d6f639..cffd11c 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -121,8 +122,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -368,6 +373,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=