From 8e0f32eb5421d71846700ffe4f11d2958a33f1b1 Mon Sep 17 00:00:00 2001 From: Terin Stock Date: Sat, 13 Jan 2018 13:23:39 -0800 Subject: feat(main): support modifying built platforms 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 --- main.go | 170 +++++++++++++++++++++++++++-------------------- main_test.go | 59 +++++++++++++++++ platforms.go | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++ platforms_defaults.go | 75 +++++++++++++++++++++ platforms_test.go | 117 +++++++++++++++++++++++++++++++++ 5 files changed, 527 insertions(+), 73 deletions(-) create mode 100644 main_test.go create mode 100644 platforms.go create mode 100644 platforms_defaults.go create mode 100644 platforms_test.go 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 +} -- cgit 1.4.1