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 }