mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-03-17 20:47:50 +02:00
feat: build of shared/static libraries (#3511)
<!-- Hi, thanks for contributing! Please make sure you read our CONTRIBUTING guide. Also, add tests and the respective documentation changes as well. --> <!-- If applied, this commit will... --> This PR improves the handling of shared or static libraries by GoReleaser. It uses the default behaviour of the Go compiler by appending the right extension to libraries. * `.so` and `.a` for Linux shared libraries and static libraries respectively * `.dylib` and `.a.` on Darwin * `.dll` and `.lib` on Windows (pre-existent) It does not add any configuration option to `.goreleaser.yml`, since it leverages the existing `buildmode` flag. Additionally, this PR takes care of adding the generated header file into the archive. <!-- Why is this change being made? --> Personally I would leverage this change to release some software both as a CLI and as a shared library. I believe others who use CGo or need interoperability with Go from other languages could benefit from this. <!-- # Provide links to any relevant tickets, URLs or other resources --> This was previously discussed in #3497. I couldn't quite think of a proper way to add some tests to the header archiving feature. Any recommendation?
This commit is contained in:
parent
25522b5c52
commit
0a536f08fd
@ -66,6 +66,12 @@ const (
|
||||
ScoopManifest
|
||||
// SBOM is a Software Bill of Materials file.
|
||||
SBOM
|
||||
// Header is a C header file, generated for CGo library builds.
|
||||
Header
|
||||
// CArchive is a C static library, generated via a CGo build with buildmode=c-archive.
|
||||
CArchive
|
||||
// CShared is a C shared library, generated via a CGo build with buildmode=c-shared.
|
||||
CShared
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
@ -104,6 +110,12 @@ func (t Type) String() string {
|
||||
return "PKGBUILD"
|
||||
case SrcInfo:
|
||||
return "SRCINFO"
|
||||
case Header:
|
||||
return "C Header"
|
||||
case CArchive:
|
||||
return "C Archive Library"
|
||||
case CShared:
|
||||
return "C Shared Library"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
|
||||
return err
|
||||
}
|
||||
|
||||
artifact := &artifact.Artifact{
|
||||
a := &artifact.Artifact{
|
||||
Type: artifact.Binary,
|
||||
Path: options.Path,
|
||||
Name: options.Name,
|
||||
@ -151,6 +151,15 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
|
||||
},
|
||||
}
|
||||
|
||||
if build.Buildmode == "c-archive" {
|
||||
a.Type = artifact.CArchive
|
||||
ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options))
|
||||
}
|
||||
if build.Buildmode == "c-shared" {
|
||||
a.Type = artifact.CShared
|
||||
ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options))
|
||||
}
|
||||
|
||||
details, err := withOverrides(ctx, build, options)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -167,7 +176,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
|
||||
"GOAMD64="+options.Goamd64,
|
||||
)
|
||||
|
||||
cmd, err := buildGoBuildLine(ctx, build, details, options, artifact, env)
|
||||
cmd, err := buildGoBuildLine(ctx, build, details, options, a, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -177,7 +186,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
|
||||
}
|
||||
|
||||
if build.ModTimestamp != "" {
|
||||
modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(artifact, map[string]string{}).Apply(build.ModTimestamp)
|
||||
modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(a, map[string]string{}).Apply(build.ModTimestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -192,7 +201,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Artifacts.Add(artifact)
|
||||
ctx.Artifacts.Add(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -206,11 +215,12 @@ func withOverrides(ctx *context.Context, build config.Build, options api.Options
|
||||
|
||||
if optsTarget == overrideTarget {
|
||||
dets := config.BuildDetails{
|
||||
Ldflags: build.BuildDetails.Ldflags,
|
||||
Tags: build.BuildDetails.Tags,
|
||||
Flags: build.BuildDetails.Flags,
|
||||
Asmflags: build.BuildDetails.Asmflags,
|
||||
Gcflags: build.BuildDetails.Gcflags,
|
||||
Buildmode: build.BuildDetails.Buildmode,
|
||||
Ldflags: build.BuildDetails.Ldflags,
|
||||
Tags: build.BuildDetails.Tags,
|
||||
Flags: build.BuildDetails.Flags,
|
||||
Asmflags: build.BuildDetails.Asmflags,
|
||||
Gcflags: build.BuildDetails.Gcflags,
|
||||
}
|
||||
if err := mergo.Merge(&dets, o.BuildDetails, mergo.WithOverride); err != nil {
|
||||
return build.BuildDetails, err
|
||||
@ -228,6 +238,9 @@ func withOverrides(ctx *context.Context, build config.Build, options api.Options
|
||||
func buildGoBuildLine(ctx *context.Context, build config.Build, details config.BuildDetails, options api.Options, artifact *artifact.Artifact, env []string) ([]string, error) {
|
||||
cmd := []string{build.GoBinary, build.Command}
|
||||
|
||||
// tags, ldflags, and buildmode, should only appear once, warning only to avoid a breaking change
|
||||
validateUniqueFlags(details)
|
||||
|
||||
flags, err := processFlags(ctx, artifact, env, details.Flags, "")
|
||||
if err != nil {
|
||||
return cmd, err
|
||||
@ -266,10 +279,28 @@ func buildGoBuildLine(ctx *context.Context, build config.Build, details config.B
|
||||
cmd = append(cmd, "-ldflags="+strings.Join(ldflags, " "))
|
||||
}
|
||||
|
||||
if details.Buildmode != "" {
|
||||
cmd = append(cmd, "-buildmode="+details.Buildmode)
|
||||
}
|
||||
|
||||
cmd = append(cmd, "-o", options.Path, build.Main)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func validateUniqueFlags(details config.BuildDetails) {
|
||||
for _, flag := range details.Flags {
|
||||
if strings.HasPrefix(flag, "-tags") && len(details.Tags) > 0 {
|
||||
log.WithField("flag", flag).WithField("tags", details.Tags).Warn("tags is defined twice")
|
||||
}
|
||||
if strings.HasPrefix(flag, "-ldflags") && len(details.Ldflags) > 0 {
|
||||
log.WithField("flag", flag).WithField("ldflags", details.Ldflags).Warn("ldflags is defined twice")
|
||||
}
|
||||
if strings.HasPrefix(flag, "-buildmode") && details.Buildmode != "" {
|
||||
log.WithField("flag", flag).WithField("buildmode", details.Buildmode).Warn("buildmode is defined twice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processFlags(ctx *context.Context, a *artifact.Artifact, env, flags []string, flagPrefix string) ([]string, error) {
|
||||
processed := make([]string, 0, len(flags))
|
||||
for _, rawFlag := range flags {
|
||||
@ -366,3 +397,26 @@ func hasMain(file *ast.File) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getHeaderArtifactForLibrary(build config.Build, options api.Options) *artifact.Artifact {
|
||||
fullPathWithoutExt := strings.TrimSuffix(options.Path, options.Ext)
|
||||
basePath := filepath.Base(fullPathWithoutExt)
|
||||
fullPath := fullPathWithoutExt + ".h"
|
||||
headerName := basePath + ".h"
|
||||
|
||||
return &artifact.Artifact{
|
||||
Type: artifact.Header,
|
||||
Path: fullPath,
|
||||
Name: headerName,
|
||||
Goos: options.Goos,
|
||||
Goarch: options.Goarch,
|
||||
Goamd64: options.Goamd64,
|
||||
Goarm: options.Goarm,
|
||||
Gomips: options.Gomips,
|
||||
Extra: map[string]interface{}{
|
||||
artifact.ExtraBinary: headerName,
|
||||
artifact.ExtraExt: ".h",
|
||||
artifact.ExtraID: build.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,9 @@ func (Pipe) Run(ctx *context.Context) error {
|
||||
filter := []artifact.Filter{artifact.Or(
|
||||
artifact.ByType(artifact.Binary),
|
||||
artifact.ByType(artifact.UniversalBinary),
|
||||
artifact.ByType(artifact.Header),
|
||||
artifact.ByType(artifact.CArchive),
|
||||
artifact.ByType(artifact.CShared),
|
||||
)}
|
||||
if len(archive.Builds) > 0 {
|
||||
filter = append(filter, artifact.ByIDs(archive.Builds...))
|
||||
|
@ -156,7 +156,7 @@ func doBuild(ctx *context.Context, build config.Build, opts builders.Options) er
|
||||
}
|
||||
|
||||
func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) {
|
||||
ext := extFor(target, build.Flags)
|
||||
ext := extFor(target, build.BuildDetails)
|
||||
parts := strings.Split(target, "_")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("%s is not a valid build target", target)
|
||||
@ -211,20 +211,35 @@ func buildOptionsForTarget(ctx *context.Context, build config.Build, target stri
|
||||
return &buildOpts, nil
|
||||
}
|
||||
|
||||
func extFor(target string, flags config.FlagArray) string {
|
||||
if strings.Contains(target, "windows") {
|
||||
for _, s := range flags {
|
||||
if s == "-buildmode=c-shared" {
|
||||
return ".dll"
|
||||
}
|
||||
if s == "-buildmode=c-archive" {
|
||||
return ".lib"
|
||||
}
|
||||
}
|
||||
return ".exe"
|
||||
}
|
||||
func extFor(target string, build config.BuildDetails) string {
|
||||
if target == "js_wasm" {
|
||||
return ".wasm"
|
||||
}
|
||||
|
||||
// Configure the extensions for shared and static libraries - by default .so and .a respectively -
|
||||
// with overrides for Windows (.dll for shared and .lib for static) and .dylib for macOS.
|
||||
buildmode := build.Buildmode
|
||||
|
||||
if buildmode == "c-shared" {
|
||||
if strings.Contains(target, "darwin") {
|
||||
return ".dylib"
|
||||
}
|
||||
if strings.Contains(target, "windows") {
|
||||
return ".dll"
|
||||
}
|
||||
return ".so"
|
||||
}
|
||||
|
||||
if buildmode == "c-archive" {
|
||||
if strings.Contains(target, "windows") {
|
||||
return ".lib"
|
||||
}
|
||||
return ".a"
|
||||
}
|
||||
|
||||
if strings.Contains(target, "windows") {
|
||||
return ".exe"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
@ -426,24 +426,47 @@ func TestSkipBuild(t *testing.T) {
|
||||
require.Len(t, ctx.Artifacts.List(), 0)
|
||||
}
|
||||
|
||||
func TestExtDarwin(t *testing.T) {
|
||||
require.Equal(t, "", extFor("darwin_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("darwin_arm64", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("darwin_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, ".dylib", extFor("darwin_amd64", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".dylib", extFor("darwin_arm64", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".a", extFor("darwin_amd64", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
require.Equal(t, ".a", extFor("darwin_arm64", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
}
|
||||
|
||||
func TestExtLinux(t *testing.T) {
|
||||
require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("linux_386", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, ".so", extFor("linux_amd64", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".so", extFor("linux_386", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".a", extFor("linux_amd64", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
require.Equal(t, ".a", extFor("linux_386", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
}
|
||||
|
||||
func TestExtWindows(t *testing.T) {
|
||||
require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{}))
|
||||
require.Equal(t, ".exe", extFor("windows_386", config.FlagArray{}))
|
||||
require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v"}))
|
||||
require.Equal(t, ".dll", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-shared"}))
|
||||
require.Equal(t, ".dll", extFor("windows_386", config.FlagArray{"-buildmode=c-shared"}))
|
||||
require.Equal(t, ".lib", extFor("windows_amd64", config.FlagArray{"-buildmode=c-archive"}))
|
||||
require.Equal(t, ".lib", extFor("windows_386", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-archive"}))
|
||||
require.Equal(t, ".exe", extFor("windows_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, ".exe", extFor("windows_386", config.BuildDetails{}))
|
||||
require.Equal(t, ".exe", extFor("windows_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, ".dll", extFor("windows_amd64", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".dll", extFor("windows_386", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".lib", extFor("windows_amd64", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
require.Equal(t, ".lib", extFor("windows_386", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
}
|
||||
|
||||
func TestExtWasm(t *testing.T) {
|
||||
require.Equal(t, ".wasm", extFor("js_wasm", config.FlagArray{}))
|
||||
require.Equal(t, ".wasm", extFor("js_wasm", config.BuildDetails{}))
|
||||
}
|
||||
|
||||
func TestExtOthers(t *testing.T) {
|
||||
require.Empty(t, "", extFor("linux_amd64", config.FlagArray{}))
|
||||
require.Empty(t, "", extFor("linuxwin_386", config.FlagArray{}))
|
||||
require.Empty(t, "", extFor("winasdasd_sad", config.FlagArray{}))
|
||||
require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("linuxwin_386", config.BuildDetails{}))
|
||||
require.Equal(t, "", extFor("winasdasd_sad", config.BuildDetails{}))
|
||||
require.Equal(t, ".so", extFor("aix_amd64", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
require.Equal(t, ".a", extFor("android_386", config.BuildDetails{Buildmode: "c-archive"}))
|
||||
require.Equal(t, ".so", extFor("winasdasd_sad", config.BuildDetails{Buildmode: "c-shared"}))
|
||||
}
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
|
@ -333,12 +333,13 @@ type BuildDetailsOverride struct {
|
||||
}
|
||||
|
||||
type BuildDetails struct {
|
||||
Ldflags StringArray `yaml:"ldflags,omitempty" json:"ldflags,omitempty"`
|
||||
Tags FlagArray `yaml:"tags,omitempty" json:"tags,omitempty"`
|
||||
Flags FlagArray `yaml:"flags,omitempty" json:"flags,omitempty"`
|
||||
Asmflags StringArray `yaml:"asmflags,omitempty" json:"asmflags,omitempty"`
|
||||
Gcflags StringArray `yaml:"gcflags,omitempty" json:"gcflags,omitempty"`
|
||||
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
|
||||
Buildmode string `yaml:"buildmode,omitempty" json:"buildmode,omitempty"`
|
||||
Ldflags StringArray `yaml:"ldflags,omitempty" json:"ldflags,omitempty"`
|
||||
Tags FlagArray `yaml:"tags,omitempty" json:"tags,omitempty"`
|
||||
Flags FlagArray `yaml:"flags,omitempty" json:"flags,omitempty"`
|
||||
Asmflags StringArray `yaml:"asmflags,omitempty" json:"asmflags,omitempty"`
|
||||
Gcflags StringArray `yaml:"gcflags,omitempty" json:"gcflags,omitempty"`
|
||||
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
|
||||
}
|
||||
|
||||
type BuildHookConfig struct {
|
||||
|
@ -51,6 +51,11 @@ builds:
|
||||
- -s -w -X main.build={{.Version}}
|
||||
- ./usemsan=-msan
|
||||
|
||||
# Custom Go build mode.
|
||||
# `c-shared` and `c-archive` configure the publishing of the header and set the correct extension.
|
||||
# Default is empty.
|
||||
buildmode: c-shared
|
||||
|
||||
# Custom build tags templates.
|
||||
# Default is empty.
|
||||
tags:
|
||||
@ -517,3 +522,29 @@ will evaluate to the list of first class ports as defined in the Go wiki.
|
||||
|
||||
You can read more about it
|
||||
[here](https://github.com/golang/go/wiki/PortingPolicy#first-class-ports).
|
||||
|
||||
## Building shared or static libraries
|
||||
|
||||
GoReleaser supports compiling and releasing C shared or static libraries,
|
||||
by configuring the [Go build mode](https://pkg.go.dev/cmd/go#hdr-Build_modes).
|
||||
|
||||
This can be set with `buildmode` in your build. It currently supports `c-shared` and `c-archive`.
|
||||
Other values will transparently be applied to the build line (via the `-buildmode` flag),
|
||||
but GoReleaser will not attempt to configure any additional logic.
|
||||
|
||||
As of today, a template may not be applied to this field.
|
||||
|
||||
GoReleaser will:
|
||||
|
||||
* set the correct file extension for the target OS.
|
||||
* package the generated header file (`.h`) in the release bundle.
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yaml
|
||||
builds:
|
||||
-
|
||||
id: "my-library"
|
||||
|
||||
# Configure the buildmode flag to output a shared library
|
||||
buildmode: "c-shared" # or "c-archive" for a static library
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user