无论您选择哪个Build Constraints,您都可以使用接口来实现这一点,并使用 New() 构造函数实现接口。并且每个特殊文件都将具有您寻找的特殊包,基于每个文件。这种方法还通过强制您仅中断您需要实现特定于每个架构的原始部分来强制执行良好的解耦。
我是文件后缀的个人粉丝,而不是构建标签,因为它可以非常容易地知道哪个文件绑定到什么架构 - 只需查看文件名。一大优点是您不必弄乱任何构建标签,它会 JustWork™。所以我下面的例子将使用文件文件后缀。具体格式为:
*_GOOS
*_GOARCH
*_GOOS_GOARCH
例如renderer_windows_amd64.go、renderer_windows_amd64_test.go、renderer_linux.go、renderer_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 与包的其余部分完全无关,并且可以通过保持您需要的任何状态用于任何架构的手段。
还要注意这里没有任何全局变量。我经常将此模式与goroutines 和channels 一起用于高并发应用程序——没有互斥锁瓶颈。让事情远离堆对于避免互斥锁定至关重要。你可以很容易地做到go r.Render() 并让它产生一个goroutine。或者,调用它几百万次。
最后,请注意上面的所有文件名如何很容易区分它们的目标平台?
不要与构建标签对抗工具,让工具为您工作。
上面的编码提示:
- 我导出了界面,
Renderer,因为所有这些都可以很容易地移到 main 之外的包中。您不想导出结构版本。但是,您可能需要导出 NewRenderer() init 方法。
- 渲染器遵循GoLang Effective Go guidelines 使用具有单一功能的简单接口:渲染。而这些函数就变成了后缀为“er”的接口名称——是的,即使名称以“er”结尾,我们在末尾加上“er”,它就变成了
type Renderer interface。 IOW:不应将其称为RenderEngine,而应使用您正在操作的单一方法将其称为Renderer:Render()。这清楚地定义了工具和代码的单一焦点。又名,“Go Way”。