github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/upx/upx.go (about)

     1  package upx
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  
     9  	"github.com/caarlos0/log"
    10  	"github.com/docker/go-units"
    11  	"github.com/goreleaser/goreleaser/internal/artifact"
    12  	"github.com/goreleaser/goreleaser/internal/pipe"
    13  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    14  	"github.com/goreleaser/goreleaser/internal/tmpl"
    15  	"github.com/goreleaser/goreleaser/pkg/config"
    16  	"github.com/goreleaser/goreleaser/pkg/context"
    17  )
    18  
    19  type Pipe struct{}
    20  
    21  func (Pipe) String() string { return "upx" }
    22  func (Pipe) Default(ctx *context.Context) error {
    23  	for i := range ctx.Config.UPXs {
    24  		upx := &ctx.Config.UPXs[i]
    25  		if upx.Binary == "" {
    26  			upx.Binary = "upx"
    27  		}
    28  	}
    29  	return nil
    30  }
    31  func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.UPXs) == 0 }
    32  func (Pipe) Run(ctx *context.Context) error {
    33  	g := semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism))
    34  	for _, upx := range ctx.Config.UPXs {
    35  		upx := upx
    36  		enabled, err := tmpl.New(ctx).Bool(upx.Enabled)
    37  		if err != nil {
    38  			return err
    39  		}
    40  		if !enabled {
    41  			return pipe.Skip("upx is not enabled")
    42  		}
    43  		if _, err := exec.LookPath(upx.Binary); err != nil {
    44  			return pipe.Skipf("%s not found in PATH", upx.Binary)
    45  		}
    46  		for _, bin := range findBinaries(ctx, upx) {
    47  			bin := bin
    48  			g.Go(func() error {
    49  				sizeBefore := sizeOf(bin.Path)
    50  				args := []string{
    51  					"--quiet",
    52  				}
    53  				switch upx.Compress {
    54  				case "best":
    55  					args = append(args, "--best")
    56  				case "":
    57  				default:
    58  					args = append(args, "-"+upx.Compress)
    59  				}
    60  				if upx.LZMA {
    61  					args = append(args, "--lzma")
    62  				}
    63  				if upx.Brute {
    64  					args = append(args, "--brute")
    65  				}
    66  				args = append(args, bin.Path)
    67  				out, err := exec.CommandContext(ctx, "upx", args...).CombinedOutput()
    68  				if err != nil {
    69  					for _, ke := range knownExceptions {
    70  						if strings.Contains(string(out), ke) {
    71  							log.WithField("binary", bin.Path).
    72  								WithField("exception", ke).
    73  								Warn("could not pack")
    74  							return nil
    75  						}
    76  					}
    77  					return fmt.Errorf("could not pack %s: %w: %s", bin.Path, err, string(out))
    78  				}
    79  
    80  				sizeAfter := sizeOf(bin.Path)
    81  
    82  				log.
    83  					WithField("before", units.HumanSize(float64(sizeBefore))).
    84  					WithField("after", units.HumanSize(float64(sizeAfter))).
    85  					WithField("ratio", fmt.Sprintf("%d%%", (sizeAfter*100)/sizeBefore)).
    86  					WithField("binary", bin.Path).
    87  					Info("packed")
    88  
    89  				return nil
    90  			})
    91  		}
    92  	}
    93  	return g.Wait()
    94  }
    95  
    96  var knownExceptions = []string{
    97  	"CantPackException",
    98  	"AlreadyPackedException",
    99  	"NotCompressibleException",
   100  }
   101  
   102  func findBinaries(ctx *context.Context, upx config.UPX) []*artifact.Artifact {
   103  	filters := []artifact.Filter{
   104  		artifact.Or(
   105  			artifact.ByType(artifact.Binary),
   106  			artifact.ByType(artifact.UniversalBinary),
   107  		),
   108  	}
   109  	if f := orBy(artifact.ByGoos, upx.Goos); f != nil {
   110  		filters = append(filters, f)
   111  	}
   112  	if f := orBy(artifact.ByGoarch, upx.Goarch); f != nil {
   113  		filters = append(filters, f)
   114  	}
   115  	if f := orBy(artifact.ByGoarm, upx.Goarm); f != nil {
   116  		filters = append(filters, f)
   117  	}
   118  	if f := orBy(artifact.ByGoamd64, upx.Goamd64); f != nil {
   119  		filters = append(filters, f)
   120  	}
   121  	if len(upx.IDs) > 0 {
   122  		filters = append(filters, artifact.ByIDs(upx.IDs...))
   123  	}
   124  	return ctx.Artifacts.Filter(artifact.And(filters...)).List()
   125  }
   126  
   127  func orBy(fn func(string) artifact.Filter, items []string) artifact.Filter {
   128  	var result []artifact.Filter
   129  	for _, f := range items {
   130  		result = append(result, fn(f))
   131  	}
   132  	if len(result) == 0 {
   133  		return nil
   134  	}
   135  	return artifact.Or(result...)
   136  }
   137  
   138  func sizeOf(name string) int64 {
   139  	st, err := os.Stat(name)
   140  	if err != nil {
   141  		return 0
   142  	}
   143  	return st.Size()
   144  }