about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTerin Stock <terinjokes@gmail.com>2018-01-13 13:23:39 -0800
committerTerin Stock <terinjokes@gmail.com>2018-01-17 13:42:24 -0800
commit8e0f32eb5421d71846700ffe4f11d2958a33f1b1 (patch)
tree33603cfe7125d0b142565b590afdfd9ea4420919
parent4dabf654cdd8d1486e8c13bd99a7749fb299db63 (diff)
feat(main): support modifying built platforms v0.1.0
Bakelite now accepts a platform modification string vita the -platform
flag. This string format for adding and removing the built platforms,
including removing the default platforms entirely. It is processed left
to right.

  bakelite -platform '-windows +linux/s390x' [packages]

Would now build [packages] for all of the default platforms, except for
Windows, and also build for the linux/s390x platform.

  bakelite -platform '- +linux +darwin' [packages]

Would now build [packages] for the default platforms of only the Linux
and Darwin operating systems.

The platform modification string is parsed and turned into calls to
a structure following the builder pattern.

As the user can now modify the built platforms, Bakelite now exits with
a non-zero exit code if any of the platforms fail to build.

Change-Id: Iade51e17bbfda4e916394343a5f8cb3208f2b160
-rw-r--r--main.go170
-rw-r--r--main_test.go59
-rw-r--r--platforms.go179
-rw-r--r--platforms_defaults.go75
-rw-r--r--platforms_test.go117
5 files changed, 527 insertions, 73 deletions
diff --git a/main.go b/main.go
index ece5f12..8096ae5 100644
--- a/main.go
+++ b/main.go
@@ -10,45 +10,12 @@ import (
 	"os/exec"
 	"path/filepath"
 	"runtime"
+	"strings"
 
+	bflag "github.com/terinjokes/bakelite/internal/flag"
 	"golang.org/x/sync/semaphore"
 )
 
-type Arch string
-
-const (
-	ARCH_AMD64    Arch = "amd64"
-	ARCH_386      Arch = "386"
-	ARCH_ARM      Arch = "arm"
-	ARCH_ARM64    Arch = "arm64"
-	ARCH_PPC64    Arch = "ppc64"
-	ARCH_PPC64LE  Arch = "ppc64le"
-	ARCH_MIPS     Arch = "mips"
-	ARCH_MIPSLE   Arch = "mipsle"
-	ARCH_MIPS64   Arch = "mips64"
-	ARCH_MIPS64LE Arch = "mips64le"
-)
-
-type OS string
-
-const (
-	OS_ANDROID   OS = "android"
-	OS_DARWIN    OS = "darwin"
-	OS_DRAGONFLY OS = "dragonfly"
-	OS_FREEBSD   OS = "freebsd"
-	OS_LINUX     OS = "linux"
-	OS_NETBSD    OS = "netbsd"
-	OS_OPENBSD   OS = "openbsd"
-	OS_PLAN9     OS = "plan9"
-	OS_SOLARIS   OS = "solaris"
-	OS_WINDOWS   OS = "windows"
-)
-
-type Platform struct {
-	OS   OS
-	Arch Arch
-}
-
 type kvs map[string]string
 
 func (o kvs) Strings() []string {
@@ -62,46 +29,13 @@ func (o kvs) Strings() []string {
 
 var cgo bool
 var ldflags string
+var platformFields []string
 
 func main() {
-	// TODO: enable ARM after supporting GOARM
-	// TODO: probably should make this configurableā€¦
-	platforms := []Platform{
-		//{OS_ANDROID, ARCH_ARM},
-		{OS_DARWIN, ARCH_386},
-		{OS_DARWIN, ARCH_AMD64},
-		//{OS_DARWIN, ARCH_ARM},
-		//{OS_DARWIN, ARCH_ARM64},
-		{OS_DRAGONFLY, ARCH_AMD64},
-		{OS_FREEBSD, ARCH_386},
-		{OS_FREEBSD, ARCH_AMD64},
-		//{OS_FREEBSD, ARCH_ARM},
-		{OS_LINUX, ARCH_386},
-		{OS_LINUX, ARCH_AMD64},
-		//{OS_LINUX, ARCH_ARM},
-		//{OS_LINUX, ARCH_ARM64},
-		{OS_LINUX, ARCH_PPC64},
-		{OS_LINUX, ARCH_PPC64LE},
-		{OS_LINUX, ARCH_MIPS},
-		{OS_LINUX, ARCH_MIPSLE},
-		{OS_LINUX, ARCH_MIPS64},
-		{OS_LINUX, ARCH_MIPS64LE},
-		{OS_NETBSD, ARCH_386},
-		{OS_NETBSD, ARCH_AMD64},
-		//{OS_NETBSD, ARCH_ARM},
-		{OS_OPENBSD, ARCH_386},
-		{OS_OPENBSD, ARCH_AMD64},
-		//{OS_OPENBSD, ARCH_ARM},
-		{OS_PLAN9, ARCH_386},
-		{OS_PLAN9, ARCH_AMD64},
-		{OS_SOLARIS, ARCH_AMD64},
-		{OS_WINDOWS, ARCH_386},
-		{OS_WINDOWS, ARCH_AMD64},
-	}
-
 	flags := flag.NewFlagSet("bakelite", flag.ExitOnError)
 	flags.BoolVar(&cgo, "cgo", false, "enables cgo (may require your own toolchain).")
 	flags.StringVar(&ldflags, "ldflags", "", "arguments to pass on each go tool compile invocation.")
+	flags.Var((*bflag.StringsValue)(&platformFields), "platforms", "modify the list of platforms built")
 	flags.Usage = func() {
 		fmt.Println("usage: bakelite [build flags] [packages]")
 		fmt.Println(`
@@ -124,8 +58,42 @@ The Bakelite specific flags:
 
 	-cgo
 		passes CGO_ENABLED=1 to the build environment.
-		May require a build toolchain for each GOOS and GOARCH
-		combination.
+		May require a build toolchain for each GOOS and GOARCH combination.
+	-platforms 'platform list'
+		modify the built platforms.
+		Platforms are prefixed with "-" to remove from the set and "+" to add
+		to the set. They can be specified sparsely as just the OS, or as a
+		complete GOOS/GOARCH declaration. If the special platform "-" is
+		provided as the first platform, the default set is disabled. See below
+		for the default list of platforms.
+
+By default Bakelite builds for the following platforms:
+
+	darwin/386
+	darwin/amd64
+	dragonfly/amd64
+	freebsd/386
+	freebsd/amd64
+	linux/386
+	linux/amd64
+	linux/ppc64
+	linux/ppc64le
+	linux/mips
+	linux/mipsle
+	linux/mips64
+	linux/mips64le
+	netbsd/386
+	netbsd/amd64
+	openbsd/386
+	openbsd/amd64
+	plan9/386
+	plan9/amd64
+	solaris/amd64
+	windows/386
+	windows/amd64
+
+All the flags that take a list of arguments accept a space-separated
+list of strings.
 
 For more about specifying packages, see 'go help packages'.
 For more about calling between Go and C/C++, run 'go help c'.
@@ -144,6 +112,18 @@ See also: go build, go install, go clean.
 		os.Exit(-1)
 	}
 
+	plBuilder, _ := NewPlatformBuilder()
+	if len(platformFields) > 0 && platformFields[0] == "-" {
+		platformFields = platformFields[1:]
+	} else {
+		plBuilder = plBuilder.WithDefaults()
+	}
+	if len(platformFields) != 0 {
+		plBuilder = parsePlatforms(plBuilder, platformFields)
+	}
+
+	platforms := plBuilder.Build()
+
 	var (
 		parallelJobs = runtime.NumCPU()
 		sem          = semaphore.NewWeighted(int64(parallelJobs))
@@ -152,22 +132,33 @@ See also: go build, go install, go clean.
 
 	fmt.Printf("info: running bakelite with %d jobs\n", parallelJobs)
 
+	var errored bool
 	for _, platform := range platforms {
 		for _, pkg := range packages {
 			if err := sem.Acquire(ctx, 1); err != nil {
 				log.Printf("failed to acquire semaphore: %s", err)
+				errored = true
 				break
 			}
 
 			go func(platform Platform, pkg string) {
 				defer sem.Release(1)
-				build(ctx, platform, pkg)
+				err := build(ctx, platform, pkg)
+
+				if err != nil {
+					errored = true
+				}
 			}(platform, pkg)
 		}
 	}
 
 	if err := sem.Acquire(ctx, int64(parallelJobs)); err != nil {
 		log.Printf("failed to acquire semaphore: %s", err)
+		errored = true
+	}
+
+	if errored {
+		os.Exit(1)
 	}
 }
 
@@ -220,3 +211,36 @@ func build(ctx context.Context, platform Platform, pkg string) error {
 
 	return err
 }
+
+func parsePlatforms(plBuilder *PlatformBuilder, fields []string) *PlatformBuilder {
+	for _, f := range fields {
+		switch f[0] {
+		case '-':
+			if strings.ContainsRune(f, '/') {
+				sp := strings.Split(f[1:], "/")
+				p := Platform{
+					OS:   OS(sp[0]),
+					Arch: Arch(sp[1]),
+				}
+
+				plBuilder = plBuilder.WithoutPlatform(p)
+			} else {
+				plBuilder = plBuilder.WithoutOS(OS(f[1:]))
+			}
+		case '+':
+			if strings.ContainsRune(f, '/') {
+				sp := strings.Split(f[1:], "/")
+				p := Platform{
+					OS:   OS(sp[0]),
+					Arch: Arch(sp[1]),
+				}
+
+				plBuilder = plBuilder.WithPlatform(p)
+			} else {
+				plBuilder = plBuilder.WithOS(OS(f[1:]))
+			}
+		}
+	}
+
+	return plBuilder
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..4d3e91e
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func TestParsePlatforms(t *testing.T) {
+	fields := []string{"+windows", "+plan9", "-windows/386"}
+	plb, _ := NewPlatformBuilder()
+
+	plb = parsePlatforms(plb, fields)
+
+	pl := plb.Build()
+	expected := []Platform{
+		{OS("windows"), Arch("amd64")},
+		{OS("plan9"), Arch("386")},
+		{OS("plan9"), Arch("amd64")},
+	}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+
+func TestParsePlatformsRemoveOS(t *testing.T) {
+	fields := []string{"+windows", "+plan9", "-windows"}
+	plb, _ := NewPlatformBuilder()
+
+	plb = parsePlatforms(plb, fields)
+
+	pl := plb.Build()
+	expected := []Platform{
+		{OS("plan9"), Arch("386")},
+		{OS("plan9"), Arch("amd64")},
+	}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+func TestParsePlatformRemoveNothing(t *testing.T) {
+	fields := []string{"+windows", "-"}
+	plb, _ := NewPlatformBuilder()
+
+	plb = parsePlatforms(plb, fields)
+
+	pl := plb.Build()
+	expected := []Platform{
+		{OS("windows"), Arch("amd64")},
+		{OS("windows"), Arch("386")},
+	}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
diff --git a/platforms.go b/platforms.go
new file mode 100644
index 0000000..9c0a572
--- /dev/null
+++ b/platforms.go
@@ -0,0 +1,179 @@
+package main
+
+// Arch represents a Go Arch.
+type Arch string
+
+const (
+	ARCH_AMD64    Arch = "amd64"
+	ARCH_386      Arch = "386"
+	ARCH_ARM      Arch = "arm"
+	ARCH_ARM64    Arch = "arm64"
+	ARCH_PPC64    Arch = "ppc64"
+	ARCH_PPC64LE  Arch = "ppc64le"
+	ARCH_MIPS     Arch = "mips"
+	ARCH_MIPSLE   Arch = "mipsle"
+	ARCH_MIPS64   Arch = "mips64"
+	ARCH_MIPS64LE Arch = "mips64le"
+)
+
+// OS represents a Go OS.
+type OS string
+
+const (
+	OS_ANDROID   OS = "android"
+	OS_DARWIN    OS = "darwin"
+	OS_DRAGONFLY OS = "dragonfly"
+	OS_FREEBSD   OS = "freebsd"
+	OS_LINUX     OS = "linux"
+	OS_NETBSD    OS = "netbsd"
+	OS_OPENBSD   OS = "openbsd"
+	OS_PLAN9     OS = "plan9"
+	OS_SOLARIS   OS = "solaris"
+	OS_WINDOWS   OS = "windows"
+)
+
+// Platform represents an OS/Arch combination.
+type Platform struct {
+	OS   OS
+	Arch Arch
+}
+
+// PlatformBuilder provides a fluent way to build up (or tear down) a list of
+// Go platforms. The built list of platforms can be retrieved from the Build method
+// of a returned builder.
+type PlatformBuilder struct {
+	platforms map[Platform]bool
+}
+
+// NewPlatformBuilder returns a PlatformBuilder with no platforms configured.
+func NewPlatformBuilder() (*PlatformBuilder, error) {
+	return &PlatformBuilder{
+		platforms: make(map[Platform]bool),
+	}, nil
+}
+
+// WithoutPlatform returns a new PlatformBuilder with platform removed.
+func (p *PlatformBuilder) WithoutPlatform(platform Platform) *PlatformBuilder {
+	pm := make(map[Platform]bool)
+
+	for k, v := range p.platforms {
+		if k != platform {
+			pm[k] = v
+		}
+	}
+
+	return &PlatformBuilder{
+		platforms: pm,
+	}
+}
+
+// WithoutOS returns a new PlatformBuilder with all platforms of the OS's
+// platforms removed.
+func (p *PlatformBuilder) WithoutOS(os OS) *PlatformBuilder {
+	pm := make(map[Platform]bool)
+
+	for k, v := range p.platforms {
+		if k.OS != os {
+			pm[k] = v
+		}
+	}
+
+	return &PlatformBuilder{
+		platforms: pm,
+	}
+}
+
+// WithPlatform returns a new PlatformBuilder with the platform added.
+func (p *PlatformBuilder) WithPlatform(platform Platform) *PlatformBuilder {
+	pm := make(map[Platform]bool)
+
+	for k, v := range p.platforms {
+		pm[k] = v
+	}
+
+	pm[platform] = true
+
+	return &PlatformBuilder{
+		platforms: pm,
+	}
+}
+
+// WithOS returns a new PlatformBuilder with the OS's default platforms added.
+func (p *PlatformBuilder) WithOS(os OS) *PlatformBuilder {
+	pm := make(map[Platform]bool)
+
+	for k, v := range p.platforms {
+		pm[k] = v
+	}
+
+	var pl []Platform
+	switch os {
+	case OS_DARWIN:
+		pl = defaultDarwin()
+	case OS_DRAGONFLY:
+		pl = defaultDragonfly()
+	case OS_FREEBSD:
+		pl = defaultFreeBSD()
+	case OS_LINUX:
+		pl = defaultLinux()
+	case OS_NETBSD:
+		pl = defaultNetBSD()
+	case OS_OPENBSD:
+		pl = defaultOpenBSD()
+	case OS_PLAN9:
+		pl = defaultPlan9()
+	case OS_SOLARIS:
+		pl = defaultSolaris()
+	case OS_WINDOWS:
+		pl = defaultWindows()
+	default:
+		pl = []Platform{}
+	}
+
+	for _, k := range pl {
+		pm[k] = true
+	}
+
+	return &PlatformBuilder{
+		platforms: pm,
+	}
+}
+
+// WithDefaults returns a new PlatformBuilder with just Bakelite's default
+// platforms.
+func (p *PlatformBuilder) WithDefaults() *PlatformBuilder {
+	pm := make(map[Platform]bool)
+
+	pls := []Platform{
+	//{OS_ANDROID, ARCH_ARM},
+	}
+
+	pls = append(pls, defaultDarwin()...)
+	pls = append(pls, defaultDragonfly()...)
+	pls = append(pls, defaultFreeBSD()...)
+	pls = append(pls, defaultLinux()...)
+	pls = append(pls, defaultNetBSD()...)
+	pls = append(pls, defaultOpenBSD()...)
+	pls = append(pls, defaultPlan9()...)
+	pls = append(pls, defaultSolaris()...)
+	pls = append(pls, defaultWindows()...)
+
+	for _, k := range pls {
+		pm[k] = true
+	}
+
+	return &PlatformBuilder{
+		platforms: pm,
+	}
+}
+
+// Build returns a new list of Platforms.
+func (p *PlatformBuilder) Build() []Platform {
+	pl := []Platform{}
+
+	for k, _ := range p.platforms {
+		pl = append(pl, k)
+	}
+
+	return pl
+}
diff --git a/platforms_defaults.go b/platforms_defaults.go
new file mode 100644
index 0000000..9219071
--- /dev/null
+++ b/platforms_defaults.go
@@ -0,0 +1,75 @@
+package main
+
+func defaultDarwin() []Platform {
+	return []Platform{
+		{OS_DARWIN, ARCH_386},
+		{OS_DARWIN, ARCH_AMD64},
+		//{OS_DARWIN, ARCH_ARM},
+		//{OS_DARWIN, ARCH_ARM64},
+	}
+}
+
+func defaultDragonfly() []Platform {
+	return []Platform{
+		{OS_DRAGONFLY, ARCH_AMD64},
+	}
+}
+
+func defaultFreeBSD() []Platform {
+	return []Platform{
+		{OS_FREEBSD, ARCH_386},
+		{OS_FREEBSD, ARCH_AMD64},
+		//{OS_FREEBSD, ARCH_ARM},
+	}
+}
+
+func defaultLinux() []Platform {
+	return []Platform{
+		{OS_LINUX, ARCH_386},
+		{OS_LINUX, ARCH_AMD64},
+		//{OS_LINUX, ARCH_ARM},
+		//{OS_LINUX, ARCH_ARM64},
+		{OS_LINUX, ARCH_PPC64},
+		{OS_LINUX, ARCH_PPC64LE},
+		{OS_LINUX, ARCH_MIPS},
+		{OS_LINUX, ARCH_MIPSLE},
+		{OS_LINUX, ARCH_MIPS64},
+		{OS_LINUX, ARCH_MIPS64LE},
+	}
+}
+
+func defaultNetBSD() []Platform {
+	return []Platform{
+		{OS_NETBSD, ARCH_386},
+		{OS_NETBSD, ARCH_AMD64},
+		//{OS_NETBSD, ARCH_ARM},
+	}
+}
+
+func defaultOpenBSD() []Platform {
+	return []Platform{
+		{OS_OPENBSD, ARCH_386},
+		{OS_OPENBSD, ARCH_AMD64},
+		//{OS_OPENBSD, ARCH_ARM},
+	}
+}
+
+func defaultPlan9() []Platform {
+	return []Platform{
+		{OS_PLAN9, ARCH_386},
+		{OS_PLAN9, ARCH_AMD64},
+	}
+}
+
+func defaultSolaris() []Platform {
+	return []Platform{
+		{OS_SOLARIS, ARCH_AMD64},
+	}
+}
+
+func defaultWindows() []Platform {
+	return []Platform{
+		{OS_WINDOWS, ARCH_386},
+		{OS_WINDOWS, ARCH_AMD64},
+	}
+}
diff --git a/platforms_test.go b/platforms_test.go
new file mode 100644
index 0000000..b2fcacc
--- /dev/null
+++ b/platforms_test.go
@@ -0,0 +1,117 @@
+package main
+
+import (
+	"fmt"
+	"sort"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func ExampleNewPlatformBuilder() {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.
+		WithOS(OS("windows")).
+		WithoutPlatform(Platform{OS("windows"), Arch("amd64")}).
+		WithPlatform(Platform{OS("plan9"), Arch("amd64")})
+
+	pl := plb.Build()
+	sort.SliceStable(pl, func(i, j int) bool {
+		return lessPlatforms(pl[i], pl[j])
+	})
+
+	fmt.Println(pl)
+
+	// Output:
+	// [{plan9 amd64} {windows 386}]
+}
+
+func TestWithOS(t *testing.T) {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.WithOS(OS("windows"))
+
+	pl := plb.Build()
+	expected := defaultWindows()
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+
+func TestWithoutOS(t *testing.T) {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.
+		WithPlatform(
+			Platform{OS("windows"), Arch("amd64")},
+		).
+		WithPlatform(
+			Platform{OS("plan9"), Arch("amd64")},
+		).
+		WithoutOS(OS("windows"))
+
+	pl := plb.Build()
+	expected := []Platform{{OS("plan9"), Arch("amd64")}}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+
+func TestWithPlatform(t *testing.T) {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.WithPlatform(Platform{OS("windows"), Arch("arm64")})
+
+	pl := plb.Build()
+	expected := []Platform{{OS("windows"), Arch("arm64")}}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+
+func TestWithoutPlaform(t *testing.T) {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.
+		WithOS(OS("windows")).
+		WithOS(OS("plan9")).
+		WithoutPlatform(Platform{OS("windows"), Arch("386")})
+
+	pl := plb.Build()
+	expected := []Platform{
+		{OS("windows"), Arch("amd64")},
+		{OS("plan9"), Arch("386")},
+		{OS("plan9"), Arch("amd64")},
+	}
+
+	if diff := cmp.Diff(expected, pl, cmpopts.SortSlices(lessPlatforms)); diff != "" {
+		t.Errorf("manifest differs. (-got +want):\n%s", diff)
+	}
+}
+
+func TestWithDefaults(t *testing.T) {
+	plb, _ := NewPlatformBuilder()
+
+	plb = plb.WithDefaults()
+
+	pl := plb.Build()
+
+	if len(pl) == 0 {
+		t.Errorf("expected more defaults!")
+	}
+}
+
+func lessPlatforms(x, y Platform) bool {
+	if x.OS < y.OS {
+		return true
+	}
+	if x.OS > y.OS {
+		return false
+	}
+	return x.Arch < y.Arch
+}