【问题标题】:Building multiple binaries using different packages and build tags使用不同的包和构建标签构建多个二进制文件
【发布时间】:2017-08-30 03:37:28
【问题描述】:

我有一个应用程序需要根据目标操作系统使用不同的包,然后生成可执行文件。核心包有一个接口,需要根据使用的包来填充。

我最近发现实现这一目标的最佳方法是使用构建标签。但我正在努力使界面由加载的包填充正确的构建标签。或者也许有更好的替代方法。

这是我想象中的样子:

【问题讨论】:

  • 填充界面是什么意思?如果您只需要分配一个值,为什么不在具有相应构建标签的正确文件中进行呢?
  • @JimB 通过填充接口我的意思是设置一个具有接口类型的变量。例如var eng RenderEngine = OpenGLESvar eng RenderEngine = OpenGL 取决于构建标签。抱歉,我意识到这并不清楚。
  • @FanusduToit 查看我对var eng RenderEngine 变量的回答,在main() 中定义为简单的r。如果你愿意的话,你可以把它放在堆上,方法是让它成为全局的,并用init()初始化它。

标签: go build interface packages


【解决方案1】:

创建两个有点像这样的文件:

// +build myBuildFlag

package mypackage

import package1

var important = package1.Foo

另一个:

// +build !myBuildFlag

package mypackage

import package2

var important = package2.Foo

现在,每当您使用 important 时,它会根据您的构建标志有所不同。

【讨论】:

  • 似乎这样可以解决问题。将尝试并返回。我有点反对全局设置变量,但不确定我是否有选择。
  • 如果更可取的话,它也可以是返回不同类型的全局函数的两个定义。我理解不情愿使用包全局变量,但这不是一成不变的。
  • @Adrian 当我尝试在我的主包和函数中打印重要变量时,我得到了 nil 值。我错过了什么?
【解决方案2】:

无论您选择哪个Build Constraints,您都可以使用接口来实现这一点,并使用 New() 构造函数实现接口。并且每个特殊文件都将具有您寻找的特殊包,基于每个文件。这种方法还通过强制您仅中断您需要实现特定于每个架构的原始部分来强制执行良好的解耦。

我是文件后缀的个人粉丝,而不是构建标签,因为它可以非常容易地知道哪个文件绑定到什么架构 - 只需查看文件名。一大优点是您不必弄乱任何构建标签,它会 JustWork™。所以我下面的例子将使用文件文件后缀。具体格式为:

*_GOOS
*_GOARCH
*_GOOS_GOARCH

例如renderer_windows_amd64.gorenderer_windows_amd64_test.gorenderer_linux.gorenderer_linux_test.go等,你可以找到all the GOOS and GOARCH that Go supports here

编辑:kiddo 笔记本电脑上的验证代码(调整构建错误)。 ;) 请注意,您不能调用go run main.go,因为该架构未知。您必须 go build && ./mybinary 在本地执行它以进行测试。

main.go

package main

import (
    "fmt"
    "os"
)

func main() {
    r, err := NewRenderer()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // call the Render() method for the specific goarch-goos.
    if err := r.Render(); err != nil {
        fmt.Println(err)
    }
}

renderer.go

这是一个只定义接口的简单文件。也许还有一些常见的枚举。

package main

// Renderer renders performs the platform-specific rendering.
type Renderer interface {
    Render() error
}

// alternatively, you could define a global renderer struct
// here to use in each of hte files below if they are always
// the same.  often not though, as you want to keep states of
// of specific architectures within each struct.
// type renderer struct {
//     ...
// }
//
// func (r *renderer) Render() error {
//  ...
// }

renderer_windows.go

包括 32 位和 64 位版本。如果您想针对特定的 64 位编译 DLL 指定 64 位,那么您可以使用 renderer_windows_amd64.go 更具体地定位。

package main

import (
    "fmt"
    // "WindowsDLLPackage"  specific package to import for Windows
)

// renderer implements Renderer interface.
type renderer struct {
    // you can include some stateful info here for Windows versions,
    // to keep it out of the global heap.
    GOOS         string
    WindowsRules bool
}

// NewRenderer instantiates a Windows version.
func NewRenderer() (Renderer, error) {
    return &renderer{
        GOOS:         "Windows",
        WindowsRules: true,
    }, nil
}

// Render renders the Windows version.
func (r *renderer) Render() error {

    // use WindowsDLLPackage.NewSomething()

    fmt.Println(r.GOOS, r.WindowsRules)
    return nil
}

renderer_linux.go

Linux 不包括 Android(也不包括 darwin,又名 macOS)版本。

package main

import (
    "fmt"
    // "LinuxPackage"  specific package to import for Linux
)

// renderer implements Renderer interface.
type renderer struct {
    // you can include some stateful info here for Linux versions,
    // to keep it out of the global heap.
    GOOS       string
    LinuxRules bool
}

// NewRenderer instantiates a Linux version.
func NewRenderer() (Renderer, error) {
    return &renderer{
        GOOS:       "Linux",
        LinuxRules: true,
    }, nil
}

// Render renders the Linux version.
func (r *renderer) Render() error {

    // use LinuxPackage.NewSomething()

    fmt.Println(r.GOOS, r.LinuxRules)
    return nil
}

renderer_android.go

仅限 Android 特定版本。

package main

import (
    "fmt"
    // "AndroidPackage"  specific package to import for Android
)

// renderer implements Renderer interface.
type renderer struct {
    // you can include some stateful info here for Android versions,
    // to keep it out of the global heap.
    GOOS         string
    AndroidRules bool
}

// NewRenderer instantiates a Android version.
func NewRenderer() (Renderer, error) {
    return &renderer{
        GOOS:         "Linux",
        AndroidRules: true,
    }, nil
}

// Render renders the Android version.
func (r *renderer) Render() error {

    // use AndroidPackage.NewSomething()

    fmt.Println(r.GOOS, r.AndroidRules)
    return nil
}

生成不同的二进制文件

剩下的就是交叉编译:

$ GOOS=windows GOARCH=amd64 go build -o mybinary.exe
$ GOOS=linux GOARCH=amd64 go build -o mybinary_linux
$ GOOS=darwin GOARCH=amd64 go build -o mybinary_macos
# and whatever u do to get ios/android builds...

注意上面的所有文件是如何成为单个package main 的一部分并且它们都存在于同一个目录中?这是因为编译器只为 GOOS(windows、linux 或 android - 你可以做 darwin、freebsd 等等)选择一个文件后缀。在编译期间,编译器仅使用该文件实现一次NewRenderer()。这也是您可以使用每个文件的特定包的方式。

还要注意func NewRenderer() (Renderer, error) 如何返回Renderer 接口,而不是renderer 结构类型。

type renderer struct 与包的其余部分完全无关,并且可以通过保持您需要的任何状态用于任何架构的手段。

还要注意这里没有任何全局变量。我经常将此模式与goroutineschannels 一起用于高并发应用程序——没有互斥锁瓶颈。让事情远离堆对于避免互斥锁定至关重要。你可以很容易地做到go r.Render() 并让它产生一个goroutine。或者,调用它几百万次。

最后,请注意上面的所有文件名如何很容易区分它们的目标平台?

不要与构建标签对抗工具,让工具为您工作

上面的编码提示:

  • 我导出了界面,Renderer,因为所有这些都可以很容易地移到 main 之外的包中。您不想导出结构版本。但是,您可能需要导出 NewRenderer() init 方法。
  • 渲染器遵循GoLang Effective Go guidelines 使用具有单一功能的简单接口:渲染。而这些函数就变成了后缀为“er”的接口名称——是的,即使名称以“er”结尾,我们在末尾加上“er”,它就变成了type Renderer interface。 IOW:不应将其称为RenderEngine,而应使用您正在操作的单一方法将其称为RendererRender()。这清楚地定义了工具和代码的单一焦点。又名,“Go Way”。

【讨论】:

  • 非常详细的答案!我想坚持构建标签而不是文件后缀的原因是因为可能添加了许多系统(每个系统都有自己的构建标签)。所以如果应用程序有 8 个系统(这应该是可能的),文件后缀会变得很难看。构建标签不仅适用于操作系统或 ARCH,还适用于同一应用程序的不同包配置。
  • 文件后缀和/或构建标签之间没有关系(你也可以组合它们)。要点是使用接口和函数在新堆栈上创建实例,而不是像全局变量那样在堆上创建实例。这种模式使您在并发期间从互斥锁中解脱出来。我只是以文件后缀为例 - 可以很容易地使用这种模式的构建标签。
【解决方案3】:

看看 Dave Chaney 的帖子,它非常清楚地解释了如何处理针对特定平台/架构的构建:https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-08
    • 2010-12-07
    • 1970-01-01
    相关资源
    最近更新 更多