mirror of
https://github.com/jimeh/emacs-builds.git
synced 2026-02-19 11:56:40 +00:00
feat(tools): add dylib-tree tool to list/filter linked dynamic libraries
This commit is contained in:
248
cmd/dylib-tree/main.go
Normal file
248
cmd/dylib-tree/main.go
Normal file
@@ -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] <binary-file> [<binary-file>]",
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ReleaseType string
|
||||
|
||||
type Release struct {
|
||||
Name string `yaml:"name"`
|
||||
Title string `yaml:"title,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user