You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-07-15 01:34:38 +02:00
chore(multiarch): helper to parse targetArchitectures (#3525)
* chore(docker): helper to parse targetArchitectures * missing files
This commit is contained in:
@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||||
|
|
||||||
|
"github.com/SAP/jenkins-library/pkg/multiarch"
|
||||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||||
|
|
||||||
"golang.org/x/mod/modfile"
|
"golang.org/x/mod/modfile"
|
||||||
@ -177,8 +178,14 @@ func runGolangBuild(config *golangBuildOptions, telemetryData *telemetry.CustomD
|
|||||||
|
|
||||||
binaries := []string{}
|
binaries := []string{}
|
||||||
|
|
||||||
for _, architecture := range config.TargetArchitectures {
|
platforms, err := multiarch.ParsePlatformStrings(config.TargetArchitectures)
|
||||||
binary, err := runGolangBuildPerArchitecture(config, utils, ldflags, architecture)
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, platform := range platforms {
|
||||||
|
binary, err := runGolangBuildPerArchitecture(config, utils, ldflags, platform)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -393,12 +400,11 @@ func prepareLdflags(config *golangBuildOptions, utils golangBuildUtils, envRootP
|
|||||||
return generatedLdflags.String(), nil
|
return generatedLdflags.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuildUtils, ldflags, architecture string) (string, error) {
|
func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuildUtils, ldflags string, architecture multiarch.Platform) (string, error) {
|
||||||
var binaryName string
|
var binaryName string
|
||||||
|
|
||||||
envVars := os.Environ()
|
envVars := os.Environ()
|
||||||
goos, goarch := splitTargetArchitecture(architecture)
|
envVars = append(envVars, fmt.Sprintf("GOOS=%v", architecture.OS), fmt.Sprintf("GOARCH=%v", architecture.Arch))
|
||||||
envVars = append(envVars, fmt.Sprintf("GOOS=%v", goos), fmt.Sprintf("GOARCH=%v", goarch))
|
|
||||||
|
|
||||||
if !config.CgoEnabled {
|
if !config.CgoEnabled {
|
||||||
envVars = append(envVars, "CGO_ENABLED=0")
|
envVars = append(envVars, "CGO_ENABLED=0")
|
||||||
@ -408,10 +414,10 @@ func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuild
|
|||||||
buildOptions := []string{"build"}
|
buildOptions := []string{"build"}
|
||||||
if len(config.Output) > 0 {
|
if len(config.Output) > 0 {
|
||||||
fileExtension := ""
|
fileExtension := ""
|
||||||
if goos == "windows" {
|
if architecture.OS == "windows" {
|
||||||
fileExtension = ".exe"
|
fileExtension = ".exe"
|
||||||
}
|
}
|
||||||
binaryName = fmt.Sprintf("%v-%v.%v%v", config.Output, goos, goarch, fileExtension)
|
binaryName = fmt.Sprintf("%v-%v.%v%v", config.Output, architecture.OS, architecture.Arch, fileExtension)
|
||||||
buildOptions = append(buildOptions, "-o", binaryName)
|
buildOptions = append(buildOptions, "-o", binaryName)
|
||||||
}
|
}
|
||||||
buildOptions = append(buildOptions, config.BuildFlags...)
|
buildOptions = append(buildOptions, config.BuildFlags...)
|
||||||
@ -423,18 +429,11 @@ func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuild
|
|||||||
if err := utils.RunExecutable("go", buildOptions...); err != nil {
|
if err := utils.RunExecutable("go", buildOptions...); err != nil {
|
||||||
log.Entry().Debugf("buildOptions: %v", buildOptions)
|
log.Entry().Debugf("buildOptions: %v", buildOptions)
|
||||||
log.SetErrorCategory(log.ErrorBuild)
|
log.SetErrorCategory(log.ErrorBuild)
|
||||||
return "", fmt.Errorf("failed to run build for %v.%v: %w", goos, goarch, err)
|
return "", fmt.Errorf("failed to run build for %v.%v: %w", architecture.OS, architecture.Arch, err)
|
||||||
}
|
}
|
||||||
return binaryName, nil
|
return binaryName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitTargetArchitecture(architecture string) (string, string) {
|
|
||||||
// architecture expected to be in format os,arch due to possibleValues check of step
|
|
||||||
|
|
||||||
architectureParts := strings.Split(architecture, ",")
|
|
||||||
return architectureParts[0], architectureParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookupPrivateModulesRepositories returns a slice of all modules that match the given glob pattern
|
// lookupPrivateModulesRepositories returns a slice of all modules that match the given glob pattern
|
||||||
func lookupGolangPrivateModulesRepositories(goModFile *modfile.File, globPattern string, utils golangBuildUtils) ([]string, error) {
|
func lookupGolangPrivateModulesRepositories(goModFile *modfile.File, globPattern string, utils golangBuildUtils) ([]string, error) {
|
||||||
if globPattern == "" {
|
if globPattern == "" {
|
||||||
|
@ -11,7 +11,9 @@ import (
|
|||||||
|
|
||||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||||
"github.com/SAP/jenkins-library/pkg/mock"
|
"github.com/SAP/jenkins-library/pkg/mock"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/multiarch"
|
||||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"golang.org/x/mod/modfile"
|
"golang.org/x/mod/modfile"
|
||||||
@ -603,7 +605,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
|
|||||||
config := golangBuildOptions{}
|
config := golangBuildOptions{}
|
||||||
utils := newGolangBuildTestsUtils()
|
utils := newGolangBuildTestsUtils()
|
||||||
ldflags := ""
|
ldflags := ""
|
||||||
architecture := "linux,amd64"
|
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
|
||||||
|
|
||||||
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -621,7 +623,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
|
|||||||
config := golangBuildOptions{BuildFlags: []string{"--flag1", "val1", "--flag2", "val2"}, Output: "testBin", Packages: []string{"./test/.."}}
|
config := golangBuildOptions{BuildFlags: []string{"--flag1", "val1", "--flag2", "val2"}, Output: "testBin", Packages: []string{"./test/.."}}
|
||||||
utils := newGolangBuildTestsUtils()
|
utils := newGolangBuildTestsUtils()
|
||||||
ldflags := "-X test=test"
|
ldflags := "-X test=test"
|
||||||
architecture := "linux,amd64"
|
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
|
||||||
|
|
||||||
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -638,7 +640,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
|
|||||||
config := golangBuildOptions{Output: "testBin"}
|
config := golangBuildOptions{Output: "testBin"}
|
||||||
utils := newGolangBuildTestsUtils()
|
utils := newGolangBuildTestsUtils()
|
||||||
ldflags := ""
|
ldflags := ""
|
||||||
architecture := "windows,amd64"
|
architecture, _ := multiarch.ParsePlatformString("windows,amd64")
|
||||||
|
|
||||||
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -653,7 +655,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
|
|||||||
utils := newGolangBuildTestsUtils()
|
utils := newGolangBuildTestsUtils()
|
||||||
utils.ShouldFailOnCommand = map[string]error{"go build": fmt.Errorf("execution error")}
|
utils.ShouldFailOnCommand = map[string]error{"go build": fmt.Errorf("execution error")}
|
||||||
ldflags := ""
|
ldflags := ""
|
||||||
architecture := "linux,amd64"
|
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
|
||||||
|
|
||||||
_, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
_, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
|
||||||
assert.EqualError(t, err, "failed to run build for linux.amd64: execution error")
|
assert.EqualError(t, err, "failed to run build for linux.amd64: execution error")
|
||||||
|
73
pkg/multiarch/multiarch.go
Normal file
73
pkg/multiarch/multiarch.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package multiarch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var knownGoos = []string{"aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos"}
|
||||||
|
var knownGoarch = []string{"386", "amd64", "amd64p32", "arm", "arm64", "arm64be", "armbe", "loong64", "mips", "mips64", "mips64le", "mips64p32", "mips64p32le", "mipsle", "ppc", "ppc64", "ppc64le", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm"}
|
||||||
|
|
||||||
|
// Platform .
|
||||||
|
type Platform struct {
|
||||||
|
OS string
|
||||||
|
Arch string
|
||||||
|
Variant string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns a string representation of the platform
|
||||||
|
func (p Platform) ToString() string {
|
||||||
|
if len(p.Variant) > 0 {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", p.OS, p.Arch, p.Variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s", p.OS, p.Arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePlatformString parses the given string and returns a platform obj
|
||||||
|
func ParsePlatformString(s string) (Platform, error) {
|
||||||
|
r := regexp.MustCompile(`(?P<os>[^,/]+)[,/](?P<arch>[^,/]+)(?:[,/](?P<variant>[^,/]+))?`)
|
||||||
|
|
||||||
|
matches := r.FindStringSubmatch(strings.ToLower(s))
|
||||||
|
|
||||||
|
if len(matches) < 2 {
|
||||||
|
return Platform{}, fmt.Errorf("unable to parse platform '%s'", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Platform{}
|
||||||
|
|
||||||
|
p.OS = strings.Trim(matches[1], " ")
|
||||||
|
|
||||||
|
if !piperutils.ContainsString(knownGoos, p.OS) {
|
||||||
|
log.Entry().Warningf("OS '%s' is unknown to us", p.OS)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Arch = strings.Trim(matches[2], " ")
|
||||||
|
|
||||||
|
if !piperutils.ContainsString(knownGoarch, p.Arch) {
|
||||||
|
log.Entry().Warningf("Architecture '%s' is unknown to us", p.Arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Variant = strings.Trim(matches[3], " ")
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePlatformStrings parses the given slice of strings and returns a slice with platform objects
|
||||||
|
func ParsePlatformStrings(ss []string) ([]Platform, error) {
|
||||||
|
pp := []Platform{}
|
||||||
|
|
||||||
|
for _, s := range ss {
|
||||||
|
if p, err := ParsePlatformString(s); err == nil {
|
||||||
|
pp = append(pp, p)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp, nil
|
||||||
|
}
|
155
pkg/multiarch/multiarch_test.go
Normal file
155
pkg/multiarch/multiarch_test.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package multiarch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlatformToString(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
uut Platform
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
uut: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "arm64",
|
||||||
|
},
|
||||||
|
expect: "linux/arm64",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uut: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "arm64",
|
||||||
|
Variant: "v8",
|
||||||
|
},
|
||||||
|
expect: "linux/arm64/v8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
t.Run(test.expect, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.expect, test.uut.ToString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePlatformString(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
description string
|
||||||
|
input string
|
||||||
|
expect Platform
|
||||||
|
expectError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "format used by golangBuild - only os + arch",
|
||||||
|
input: "linux,amd64",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "format used by kanikoExecute - os/arch/variant",
|
||||||
|
input: "linux/amd64/v8",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
Variant: "v8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "sth in between - os,arch,variant",
|
||||||
|
input: "linux,amd64,v8",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
Variant: "v8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "should be case insensitive",
|
||||||
|
input: "LINUX/AMD64/V8",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
Variant: "v8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "whitespaces shall be trimmed",
|
||||||
|
input: " linux/ amd64 / v8",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
Variant: "v8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "reads unsupported values",
|
||||||
|
input: "myfancyos/runningonafancyarchitecture",
|
||||||
|
expect: Platform{
|
||||||
|
OS: "myfancyos",
|
||||||
|
Arch: "runningonafancyarchitecture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "os + arch are required, variant is optional",
|
||||||
|
input: "linux",
|
||||||
|
expectError: "unable to parse platform 'linux'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
p, err := ParsePlatformString(test.input)
|
||||||
|
|
||||||
|
if len(test.expectError) > 0 {
|
||||||
|
assert.EqualError(t, err, test.expectError)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.expect, p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePlatformStringStrings(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
description string
|
||||||
|
inputs []string
|
||||||
|
expect []Platform
|
||||||
|
expectError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "format used by golangBuild - only os + arch",
|
||||||
|
inputs: []string{"linux,amd64", "windows,amd64"},
|
||||||
|
expect: []Platform{
|
||||||
|
Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Arch: "amd64",
|
||||||
|
},
|
||||||
|
Platform{
|
||||||
|
OS: "windows",
|
||||||
|
Arch: "amd64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
p, err := ParsePlatformStrings(test.inputs)
|
||||||
|
|
||||||
|
if len(test.expectError) > 0 {
|
||||||
|
assert.EqualError(t, err, test.expectError)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.expect, p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user