Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/multibuild/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package main

//go:multibuild:output=bin/${TARGET}-${GOOS}-${GOARCH}
//go:multibuild:format=raw,tar.gz,zip

import (
"fmt"
Expand Down
59 changes: 54 additions & 5 deletions cmd/multibuild/multibuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package main

import (
"archive/tar"
"archive/zip"
"bufio"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -126,21 +129,67 @@ func doMultibuild(args cliArgs) {
buildArgs = append(buildArgs, args.goBuildArgs...)

wg.Add(1) // acquire for global
go func(goos, goarch string, buildArgs []string) {
go func(out, goos, goarch string, buildArgs []string) {
if args.verbose {
fmt.Fprintf(os.Stderr, "%s/%s: waiting\n", goos, goarch)
}
sem <- struct{}{} // acquire for job
if args.verbose {
fmt.Fprintf(os.Stderr, "%s/%s: building\n", goos, goarch)
fmt.Fprintf(os.Stderr, "%s/%s: build\n", goos, goarch)
}
runBuild(buildArgs, goos, goarch)
if args.verbose {
fmt.Fprintf(os.Stderr, "%s/%s: done\n", goos, goarch)
if len(opts.Format) > 1 {
if args.verbose {
fmt.Fprintf(os.Stderr, "%s/%s: archive\n", goos, goarch)
}

// FIXME: windows case out has .exe, and we don't want that here.
for _, format := range opts.Format {
fmt.Printf("Producing format %s\n", format)
switch format {
case formatRaw:
// already built (obvs)..
case formatZip:
arPath := out + ".zip"
fmt.Printf("arPath %s\n", arPath)
f, _ := os.Create(arPath)
defer f.Close()

zw := zip.NewWriter(f)
defer zw.Close()

w, _ := zw.Create(out)
bin, _ := os.Open(out)
defer bin.Close()
_, _ = io.Copy(w, bin)
case formatTgz:
arPath := out + ".tar.gz"
fmt.Printf("arPath %s\n", arPath)
f, _ := os.Create(arPath)
defer f.Close()

gz := gzip.NewWriter(f)
defer gz.Close()

tw := tar.NewWriter(gz)
defer tw.Close()

st, err := os.Stat(out)
fmt.Printf("St: %v err %v\n", st, err)
bin, _ := os.Open(out)
defer bin.Close()

hdr := &tar.Header{Name: out, Mode: 0644, Size: st.Size()}
tw.WriteHeader(hdr)
_, _ = io.Copy(tw, bin)
}
}

// FIXME: If raw isn't wanted, we need to subsequently remove it here.
}
<-sem // release for job
wg.Done() // release for global
}(goos, goarch, buildArgs)
}(out, goos, goarch, buildArgs)
}

wg.Wait()
Expand Down
59 changes: 58 additions & 1 deletion cmd/multibuild/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,23 @@ type target string
// e.g. ${TARGET}_${GOOS}_${GOARCH}
type outputTemplate string

// raw, tar.gz, ...
type format string

const (
formatRaw format = "raw"
formatZip = "zip"
formatTgz = "tar.gz"
)

// All options for multibuild go here..
type options struct {
// Output format
// Output filename format
Output outputTemplate

// Output formats to produce
Format []format

// Targets to include
Include []filter

Expand Down Expand Up @@ -171,6 +183,30 @@ func validateTemplate(s string) (outputTemplate, error) {
return outputTemplate(s), nil
}

// Validates that the 's' is a list of formats.
func validateFormatString(s string) ([]format, error) {
if s == "" {
return nil, fmt.Errorf("empty string is not a valid format")
}

var allowedFormats = map[format]struct{}{
formatRaw: {},
formatZip: {},
formatTgz: {},
}

var formats []format
formatStrs := strings.SplitSeq(s, ",")
for formatStr := range formatStrs {
format := format(formatStr)
if _, ok := allowedFormats[format]; !ok {
return nil, fmt.Errorf("format %q is not valid", formatStr)
}
formats = append(formats, format)
}
return formats, nil
}

func validateFilterString(s string) ([]filter, error) {
isAlphaNum := func(b byte) bool {
return (b >= 'a' && b <= 'z') ||
Expand Down Expand Up @@ -274,6 +310,19 @@ func scanBuildPath(reader io.Reader, path string) (options, error) {
return options{}, fmt.Errorf("%s:%d: go:multibuild:output=%s is invalid: %s", path, i, rest, err)
}
opts.Output = parsed
} else if strings.HasPrefix(line, "//go:multibuild:format=") {
if dlog {
log.Printf("Found format: %s:%d: %s", path, i, line)
}
rest := strings.TrimPrefix(line, "//go:multibuild:format=")
if len(opts.Format) > 0 {
return options{}, fmt.Errorf("%s:%d: go:multibuild:format was already set to %s, found: %q here", path, i, opts.Format, rest)
}
parsed, err := validateFormatString(rest)
if err != nil {
return options{}, fmt.Errorf("%s:%d: go:multibuild:format=%s is invalid: %s", path, i, rest, err)
}
opts.Format = parsed
} else if strings.HasPrefix(line, "//go:multibuild:include=") {
if dlog {
log.Printf("Found include: %s:%d: %s", path, i, line)
Expand Down Expand Up @@ -321,6 +370,11 @@ func scanBuildDir(sources []string) (options, error) {
} else if len(topts.Output) > 0 {
opts.Output = topts.Output
}
if len(opts.Format) > 0 && len(topts.Format) > 0 {
return options{}, fmt.Errorf("%s: format= already set elsewhere", path)
} else if len(topts.Format) > 0 {
opts.Format = topts.Format
}
opts.Exclude = append(opts.Exclude, topts.Exclude...)
opts.Include = append(opts.Include, topts.Include...)
}
Expand All @@ -329,6 +383,9 @@ func scanBuildDir(sources []string) (options, error) {
if len(opts.Include) == 0 {
opts.Include = []filter{"*/*"}
}
if len(opts.Format) == 0 {
opts.Format = []format{formatRaw}
}

// These require CGO_ENABLED=1, which I don't want to touch right now.
// As I don't have a use for it, let's just disable them.
Expand Down
Loading