我有一个应用程序,需要根据目标操作系统使用不同的包,然后生成可执行文件。核心软件包具有一个接口,需要根据所使用的软件包进行填充。
我最近发现实现这一目标的最佳方法是使用构建代码。但我正在努力解决的问题是使用正确的构建标记来获取已加载包的接口。或许还有更好的替代方法。
答案 0 :(得分:3)
创建两个文件,如下所示:
// +build myBuildFlag
package mypackage
import package1
var important = package1.Foo
其他人:
// +build !myBuildFlag
package mypackage
import package2
var important = package2.Foo
现在每当你使用important
时,它会根据你的构建标志而有所不同。
答案 1 :(得分:3)
无论您选择哪个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。
go run main.go
。您必须go build && ./mybinary
在本地执行测试。
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)
}
}
这是一个只定义界面的简单文件。也许是一些常见的枚举。
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 {
// ...
// }
包含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
}
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
}
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方法。type Renderer interface
。 IOW:它不应该被称为RenderEngine
,而应该使用您正在操作的单个方法调用Renderer
:Render()
。这清楚地定义了工具和代码的单一焦点。阿卡,“Go Way。”答案 2 :(得分:0)
看看Dave Chaney的帖子,该帖子解释了如何明确地处理特定平台/架构的构建:https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool