github.com/Songmu/goxz@v0.9.1/goxz.go (about)

     1  package goxz
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/sync/errgroup"
    15  )
    16  
    17  // Run the goxz
    18  func Run(ctx context.Context, args []string, outStream, errStream io.Writer) error {
    19  	return (&cli{outStream: outStream, errStream: errStream}).run(args)
    20  }
    21  
    22  type goxz struct {
    23  	os, arch                                    string
    24  	name, version                               string
    25  	dest                                        string
    26  	include                                     string
    27  	output                                      string
    28  	buildLdFlags, buildTags, buildInstallSuffix string
    29  	zipAlways                                   bool
    30  	pkgs                                        []string
    31  	static                                      bool
    32  	work                                        bool
    33  	trimpath                                    bool
    34  
    35  	platforms []*platform
    36  	projDir   string
    37  	workDir   string
    38  	resources []string
    39  }
    40  
    41  func (gx *goxz) run() error {
    42  	err := gx.init()
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	gx.workDir, err = ioutil.TempDir(gx.dest, ".goxz-")
    48  	if err != nil {
    49  		return err
    50  	}
    51  	defer func() {
    52  		if !gx.work {
    53  			os.RemoveAll(gx.workDir)
    54  		}
    55  	}()
    56  	if gx.work {
    57  		log.Printf("working dir: %s\n", gx.workDir)
    58  	}
    59  	wd, err := filepath.Abs(".")
    60  	if err != nil {
    61  		return err
    62  	}
    63  	if wd != gx.projDir {
    64  		if err := os.Chdir(gx.projDir); err != nil {
    65  			return err
    66  		}
    67  		defer os.Chdir(wd)
    68  	}
    69  	err = gx.buildAll()
    70  	if err == nil {
    71  		log.Println("Success!")
    72  	}
    73  	return err
    74  }
    75  
    76  func (gx *goxz) init() error {
    77  	log.Println("Initializing...")
    78  	if len(gx.pkgs) == 0 {
    79  		gx.pkgs = append(gx.pkgs, ".")
    80  	}
    81  	if len(gx.pkgs) > 1 && gx.output != "" {
    82  		return errors.New("When building multiple packages, output(`-o`) doesn't work")
    83  	}
    84  
    85  	if gx.projDir == "" {
    86  		var err error
    87  		gx.projDir, err = filepath.Abs(".")
    88  		if err != nil {
    89  			return err
    90  		}
    91  	} else if !filepath.IsAbs(gx.projDir) {
    92  		p, err := filepath.Abs(gx.projDir)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		gx.projDir = p
    97  	}
    98  
    99  	if gx.name == "" {
   100  		gx.name = filepath.Base(gx.projDir)
   101  	}
   102  
   103  	if err := gx.initDest(); err != nil {
   104  		return err
   105  	}
   106  	err := os.MkdirAll(gx.dest, 0755)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	// fill the defaults
   112  	if gx.os == "" {
   113  		gx.os = "linux darwin windows"
   114  	}
   115  	if gx.arch == "" {
   116  		gx.arch = "amd64 arm64"
   117  	}
   118  	gx.platforms, err = resolvePlatforms(gx.os, gx.arch)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	gx.resources, err = gx.gatherResources()
   124  	if err != nil {
   125  		return err
   126  	}
   127  	rBaseNames := make([]string, len(gx.resources))
   128  	for i, r := range gx.resources {
   129  		rBaseNames[i], _ = filepath.Rel(gx.projDir, r)
   130  	}
   131  	log.Printf("Resources to include: [%s]\n", strings.Join(rBaseNames, " "))
   132  	return nil
   133  }
   134  
   135  var separateReg = regexp.MustCompile(`\s*(?:\s+|,)\s*`)
   136  
   137  func resolvePlatforms(os, arch string) ([]*platform, error) {
   138  	platforms := []*platform{}
   139  	osTargets := separateReg.Split(os, -1)
   140  	archTargets := separateReg.Split(arch, -1)
   141  	for _, os := range osTargets {
   142  		if strings.TrimSpace(os) == "" {
   143  			continue
   144  		}
   145  		for _, arch := range archTargets {
   146  			if strings.TrimSpace(arch) == "" {
   147  				continue
   148  			}
   149  			platforms = append(platforms, &platform{os: os, arch: arch})
   150  		}
   151  	}
   152  	uniqPlatforms := []*platform{}
   153  	seen := make(map[string]struct{})
   154  	for _, pf := range platforms {
   155  		key := pf.os + ":" + pf.arch
   156  		_, ok := seen[key]
   157  		if !ok {
   158  			seen[key] = struct{}{}
   159  			uniqPlatforms = append(uniqPlatforms, pf)
   160  		}
   161  	}
   162  	return uniqPlatforms, nil
   163  }
   164  
   165  func (gx *goxz) initDest() error {
   166  	if gx.dest == "" {
   167  		gx.dest = "goxz"
   168  	}
   169  	if !filepath.IsAbs(gx.dest) {
   170  		var err error
   171  		gx.dest, err = filepath.Abs(gx.dest)
   172  		if err != nil {
   173  			return err
   174  		}
   175  	}
   176  	return nil
   177  }
   178  
   179  var (
   180  	resourceReg = regexp.MustCompile(`(?i)^(?:readme|licen[sc]e|credits?|install|changelog)(?:\.|$)`)
   181  	execExtReg  = regexp.MustCompile(`(?i)\.(?:[a-z]*sh|p[ly]|rb|exe|go)$`)
   182  )
   183  
   184  func (gx *goxz) gatherResources() ([]string, error) {
   185  	dir := gx.projDir
   186  
   187  	var ret []string
   188  	files, err := ioutil.ReadDir(dir)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	for _, f := range files {
   193  		if !f.Mode().IsRegular() {
   194  			continue
   195  		}
   196  		n := f.Name()
   197  		if resourceReg.MatchString(n) && !execExtReg.MatchString(n) {
   198  			ret = append(ret, filepath.Join(dir, n))
   199  		}
   200  	}
   201  
   202  	if gx.include != "" {
   203  		for _, inc := range separateReg.Split(gx.include, -1) {
   204  			if !filepath.IsAbs(inc) {
   205  				inc = filepath.Join(dir, inc)
   206  			}
   207  			files, err := filepath.Glob(inc)
   208  			if err != nil {
   209  				return nil, err
   210  			}
   211  			for _, f := range files {
   212  				if !filepath.IsAbs(f) {
   213  					var err error
   214  					f, err = filepath.Abs(f)
   215  					if err != nil {
   216  						return nil, err
   217  					}
   218  				}
   219  				ret = append(ret, f)
   220  			}
   221  		}
   222  	}
   223  
   224  	seen := make(map[string]struct{})
   225  	ret2 := make([]string, 0, len(ret))
   226  	for _, p := range ret {
   227  		_, ok := seen[p]
   228  		if !ok {
   229  			seen[p] = struct{}{}
   230  			ret2 = append(ret2, p)
   231  		}
   232  	}
   233  	return ret2, nil
   234  }
   235  
   236  func (gx *goxz) buildAll() error {
   237  	eg := errgroup.Group{}
   238  	for _, bdr := range gx.builders() {
   239  		bdr := bdr
   240  		eg.Go(func() error {
   241  			archivePath, err := bdr.build()
   242  			if err != nil {
   243  				return err
   244  			}
   245  			installPath := filepath.Join(gx.dest, filepath.Base(archivePath))
   246  			err = os.Rename(archivePath, installPath)
   247  			if err != nil {
   248  				return err
   249  			}
   250  			log.Printf("Artifact archived to %s\n", installPath)
   251  			return nil
   252  		})
   253  	}
   254  	return eg.Wait()
   255  }
   256  
   257  func (gx *goxz) builders() []*builder {
   258  	builders := make([]*builder, len(gx.platforms))
   259  	for i, pf := range gx.platforms {
   260  		builders[i] = &builder{
   261  			platform:           pf,
   262  			name:               gx.name,
   263  			version:            gx.version,
   264  			output:             gx.output,
   265  			buildLdFlags:       gx.buildLdFlags,
   266  			buildTags:          gx.buildTags,
   267  			buildInstallSuffix: gx.buildInstallSuffix,
   268  			pkgs:               gx.pkgs,
   269  			zipAlways:          gx.zipAlways,
   270  			static:             gx.static,
   271  			workDirBase:        gx.workDir,
   272  			trimpath:           gx.trimpath,
   273  			resources:          gx.resources,
   274  			projDir:            gx.projDir,
   275  		}
   276  	}
   277  	return builders
   278  }