// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package mage import ( "sort" "strings" "github.com/pkg/errors" ) // BuildPlatforms is a list of GOOS/GOARCH pairs supported by Go. // The list originated from 'go tool dist list -json'. var BuildPlatforms = BuildPlatformList{ {"android/386", CGOSupported}, {"android/amd64", CGOSupported}, {"android/arm", CGOSupported}, {"android/arm64", CGOSupported}, {"darwin/386", CGOSupported | CrossBuildSupported}, {"darwin/amd64", CGOSupported | CrossBuildSupported | Default}, {"darwin/arm", CGOSupported}, {"darwin/arm64", CGOSupported}, {"dragonfly/amd64", CGOSupported}, {"freebsd/386", CGOSupported}, {"freebsd/amd64", CGOSupported}, {"freebsd/arm", 0}, {"linux/386", CGOSupported | CrossBuildSupported | Default}, {"linux/amd64", CGOSupported | CrossBuildSupported | Default}, {"linux/armv5", CGOSupported | CrossBuildSupported}, {"linux/armv6", CGOSupported | CrossBuildSupported}, {"linux/armv7", CGOSupported | CrossBuildSupported}, {"linux/arm64", CGOSupported | CrossBuildSupported}, {"linux/mips", CGOSupported | CrossBuildSupported}, {"linux/mips64", CGOSupported | CrossBuildSupported}, {"linux/mips64le", CGOSupported | CrossBuildSupported}, {"linux/mipsle", CGOSupported | CrossBuildSupported}, {"linux/ppc64", CrossBuildSupported}, {"linux/ppc64le", CGOSupported | CrossBuildSupported}, {"linux/s390x", CGOSupported | CrossBuildSupported}, {"nacl/386", 0}, {"nacl/amd64p32", 0}, {"nacl/arm", 0}, {"netbsd/386", CGOSupported}, {"netbsd/amd64", CGOSupported}, {"netbsd/arm", CGOSupported}, {"openbsd/386", CGOSupported}, {"openbsd/amd64", CGOSupported}, {"openbsd/arm", 0}, {"plan9/386", 0}, {"plan9/amd64", 0}, {"plan9/arm", 0}, {"solaris/amd64", CGOSupported}, {"windows/386", CGOSupported | CrossBuildSupported | Default}, {"windows/amd64", CGOSupported | CrossBuildSupported | Default}, } // PlatformFeature specifies features that are supported for a platform. type PlatformFeature uint8 // List of PlatformFeature types. const ( CGOSupported PlatformFeature = 1 << iota // CGO is supported. CrossBuildSupported // Cross-build supported by golang-crossbuild. Default // Built by default on crossBuild and package. ) var platformFlagNames = map[PlatformFeature]string{ CGOSupported: "cgo", CrossBuildSupported: "xbuild", Default: "default", } // String returns a string representation of the platform features. func (f PlatformFeature) String() string { if f == 0 { return "none" } var names []string for value, name := range platformFlagNames { if f&value > 0 { names = append(names, name) } } return strings.Join(names, "|") } // CanCrossBuild returns true if cross-building is supported by // golang-crossbuild. func (f PlatformFeature) CanCrossBuild() bool { return f&CrossBuildSupported > 0 } // SupportsCGO returns true if CGO is supported. func (f PlatformFeature) SupportsCGO() bool { return f&CGOSupported > 0 } // BuildPlatform represents a target platform for builds. type BuildPlatform struct { Name string Flags PlatformFeature } // GOOS returns the GOOS value contained in the name. func (p BuildPlatform) GOOS() string { idx := strings.IndexByte(p.Name, '/') if idx == -1 { return p.Name } return p.Name[:idx] } // Arch returns the architecture value contained in the name. func (p BuildPlatform) Arch() string { idx := strings.IndexByte(p.Name, '/') if idx == -1 { return "" } return p.Name[strings.IndexByte(p.Name, '/')+1:] } // GOARCH returns the GOARCH value associated with the architecture contained // in the name. For ARM the Arch and GOARCH can differ because the GOARM value // is encoded in the Arch value. func (p BuildPlatform) GOARCH() string { // Allow armv7 to be interpreted as GOARCH=arm GOARM=7. arch := p.Arch() if strings.HasPrefix(arch, "armv") { return "arm" } return arch } // GOARM returns the ARM version. func (p BuildPlatform) GOARM() string { arch := p.Arch() if strings.HasPrefix(arch, "armv") { return strings.TrimPrefix(arch, "armv") } return "" } // Attributes returns a new PlatformAttributes. func (p BuildPlatform) Attributes() PlatformAttributes { return MakePlatformAttributes(p.GOOS(), p.GOARCH(), p.GOARM()) } // PlatformAttributes contains all of the data that can be extracted from a // BuildPlatform name. type PlatformAttributes struct { Name string GOOS string GOARCH string GOARM string Arch string } // MakePlatformAttributes returns a new PlatformAttributes. func MakePlatformAttributes(goos, goarch, goarm string) PlatformAttributes { arch := goarch if goarch == "arm" && goarm != "" { arch += "v" + goarm } name := goos if arch != "" { name += "/" + arch } return PlatformAttributes{ Name: name, GOOS: goos, GOARCH: goarch, GOARM: goarm, Arch: arch, } } // String returns the string representation of the platform which has the format // of "GOOS/Arch". func (p PlatformAttributes) String() string { return p.Name } // BuildPlatformList is a list of BuildPlatforms that supports filtering. type BuildPlatformList []BuildPlatform // Get returns the BuildPlatform matching the given name. func (list BuildPlatformList) Get(name string) (BuildPlatform, bool) { for _, bp := range list { if bp.Name == name { return bp, true } } return BuildPlatform{}, false } // Defaults returns the default platforms contained in the list. func (list BuildPlatformList) Defaults() BuildPlatformList { return list.filter(func(p BuildPlatform) bool { return p.Flags&Default > 0 }) } // CrossBuild returns the platforms that support cross-building. func (list BuildPlatformList) CrossBuild() BuildPlatformList { return list.filter(func(p BuildPlatform) bool { return p.Flags&CrossBuildSupported > 0 }) } // filter returns the platforms that match the given predicate. func (list BuildPlatformList) filter(pred func(p BuildPlatform) bool) BuildPlatformList { var out BuildPlatformList for _, item := range list { if pred(item) { out = append(out, item) } } return out } // Remove returns a copy of list without platforms matching name. func (list BuildPlatformList) Remove(name string) BuildPlatformList { attrs := BuildPlatform{Name: name}.Attributes() if attrs.Arch == "" { // Filter by GOOS only. return list.filter(func(bp BuildPlatform) bool { return bp.GOOS() != attrs.GOOS }) } return list.filter(func(bp BuildPlatform) bool { return !(bp.GOOS() == attrs.GOOS && bp.Arch() == attrs.Arch) }) } // Select returns a new list containing the platforms that match name. func (list BuildPlatformList) Select(name string) BuildPlatformList { attrs := BuildPlatform{Name: name}.Attributes() if attrs.Arch == "" { // Filter by GOOS only. return list.filter(func(bp BuildPlatform) bool { return bp.GOOS() == attrs.GOOS }) } return list.filter(func(bp BuildPlatform) bool { return bp.GOOS() == attrs.GOOS && bp.Arch() == attrs.Arch }) } type platformExpression struct { Add []string Select []string SelectCrossBuild bool Remove []string } func newPlatformExpression(expr string) (*platformExpression, error) { if strings.TrimSpace(expr) == "" { return nil, nil } pe := &platformExpression{} // Parse the expression. words := strings.FieldsFunc(expr, isSeparator) for _, w := range words { if strings.HasPrefix(w, "+") { pe.Add = append(pe.Add, strings.TrimPrefix(w, "+")) } else if strings.HasPrefix(w, "!") { pe.Remove = append(pe.Remove, strings.TrimPrefix(w, "!")) } else if w == "xbuild" { pe.SelectCrossBuild = true } else { pe.Select = append(pe.Select, w) } } // Validate the names used. checks := make([]string, 0, len(pe.Add)+len(pe.Select)+len(pe.Remove)) checks = append(checks, pe.Add...) checks = append(checks, pe.Select...) checks = append(checks, pe.Remove...) for _, name := range checks { if name == "all" || name == "defaults" { continue } var valid bool for _, bp := range BuildPlatforms { if bp.Name == name || bp.GOOS() == name { valid = true break } } if !valid { return nil, errors.Errorf("invalid platform in expression: %v", name) } } return pe, nil } // NewPlatformList returns a new BuildPlatformList based on given expression. // // By default the initial set include only the platforms designated as defaults. // To add additional platforms to list use an addition term that is designated // with a plug sign (e.g. "+netbsd" or "+linux/armv7"). Or you may use "+all" // to change the initial set to include all possible platforms then filter // from there (e.g. "+all linux windows"). // // The expression can consists of selections (e.g. "linux") and/or // removals (e.g."!windows"). Each term can be valid GOOS or a valid GOOS/Arch // pair. // // "xbuild" is a special selection term used to select all platforms that are // cross-build eligible. // "defaults" is a special selection or removal term that contains all platforms // designated as a default. // "all" is a special addition term for adding all valid GOOS/Arch pairs to the // set. func NewPlatformList(expr string) BuildPlatformList { pe, err := newPlatformExpression(expr) if err != nil { panic(err) } if pe == nil { return BuildPlatforms.Defaults() } var out BuildPlatformList if len(pe.Add) == 0 || (len(pe.Select) == 0 && len(pe.Remove) == 0) { // Bootstrap list with default platforms when the expression is // exclusively adds OR exclusively selects and removes. out = BuildPlatforms.Defaults() } all := BuildPlatforms for _, name := range pe.Add { if name == "all" { out = make(BuildPlatformList, len(all)) copy(out, all) break } out = append(out, all.Select(name)...) } if len(pe.Select) > 0 { var selected BuildPlatformList for _, name := range pe.Select { selected = append(selected, out.Select(name)...) } out = selected } for _, name := range pe.Remove { if name == "defaults" { for _, defaultBP := range all.Defaults() { out = out.Remove(defaultBP.Name) } continue } out = out.Remove(name) } if pe.SelectCrossBuild { out = out.CrossBuild() } return out.deduplicate() } // Filter creates a new list based on the provided expression. // // The expression can consists of selections (e.g. "linux") and/or // removals (e.g."!windows"). Each term can be valid GOOS or a valid GOOS/Arch // pair. // // "xbuild" is a special selection term used to select all platforms that are // cross-build eligible. // "defaults" is a special selection or removal term that contains all platforms // designated as a default. func (list BuildPlatformList) Filter(expr string) BuildPlatformList { pe, err := newPlatformExpression(expr) if err != nil { panic(err) } if pe == nil { return list } if len(pe.Add) > 0 { panic(errors.Errorf("adds (%v) cannot be used in filter expressions", strings.Join(pe.Add, ", "))) } var out BuildPlatformList if len(pe.Select) == 0 && !pe.SelectCrossBuild { // Filter is only removals so clone the original list. out = append(out, list...) } if pe.SelectCrossBuild { out = append(out, list.CrossBuild()...) } for _, name := range pe.Select { if name == "defaults" { out = append(out, list.Defaults()...) continue } out = append(out, list.Select(name)...) } for _, name := range pe.Remove { if name == "defaults" { for _, defaultBP := range BuildPlatforms.Defaults() { out = out.Remove(defaultBP.Name) } continue } out = out.Remove(name) } return out.deduplicate() } // deduplicate removes duplicate platforms and sorts the list. func (list BuildPlatformList) deduplicate() BuildPlatformList { set := map[string]BuildPlatform{} for _, item := range list { set[item.Name] = item } var out BuildPlatformList for _, v := range set { out = append(out, v) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out }