mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 09:25:38 +00:00
cmd/gogio: add support for Windows
Now, gogio can build the program for Windows, using the `-target windows`. It will build with `-H=windowsgui`, by default. Also, it can compile for multiple platforms if specified using `-target` (e.g. `-target arm, 386, amd64`), the executable will have the respective suffix (i.e. `_386.exe`). gogio will also attach (any) appicon.png as executable icon resource and include some information about the file and supported operating system. Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
@@ -97,7 +97,6 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
|
|||||||
buildtools: buildtools,
|
buildtools: buildtools,
|
||||||
androidjar: filepath.Join(platform, "android.jar"),
|
androidjar: filepath.Join(platform, "android.jar"),
|
||||||
}
|
}
|
||||||
|
|
||||||
perms := []string{"default"}
|
perms := []string{"default"}
|
||||||
const permPref = "gioui.org/app/permission/"
|
const permPref = "gioui.org/app/permission/"
|
||||||
cfg := &packages.Config{
|
cfg := &packages.Config{
|
||||||
@@ -176,11 +175,15 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
minSDK := 16
|
||||||
|
if bi.minsdk > minSDK {
|
||||||
|
minSDK = bi.minsdk
|
||||||
|
}
|
||||||
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
|
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
|
||||||
var builds errgroup.Group
|
var builds errgroup.Group
|
||||||
for _, a := range bi.archs {
|
for _, a := range bi.archs {
|
||||||
arch := allArchs[a]
|
arch := allArchs[a]
|
||||||
clang, err := latestCompiler(tcRoot, a, bi.minsdk)
|
clang, err := latestCompiler(tcRoot, a, minSDK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s. Please make sure you have NDK >= r19c installed. Use the command `sdkmanager ndk-bundle` to install it.", err)
|
return fmt.Errorf("%s. Please make sure you have NDK >= r19c installed. Use the command `sdkmanager ndk-bundle` to install it.", err)
|
||||||
}
|
}
|
||||||
@@ -351,13 +354,9 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
icon := *iconPath
|
|
||||||
if icon == "" {
|
|
||||||
icon = filepath.Join(bi.pkgDir, "appicon.png")
|
|
||||||
}
|
|
||||||
iconSnip := ""
|
iconSnip := ""
|
||||||
if _, err := os.Stat(icon); err == nil {
|
if _, err := os.Stat(bi.iconPath); err == nil {
|
||||||
err := buildIcons(resDir, icon, []iconVariant{
|
err := buildIcons(resDir, bi.iconPath, []iconVariant{
|
||||||
{path: filepath.Join("mipmap-hdpi", "ic_launcher.png"), size: 72},
|
{path: filepath.Join("mipmap-hdpi", "ic_launcher.png"), size: 72},
|
||||||
{path: filepath.Join("mipmap-xhdpi", "ic_launcher.png"), size: 96},
|
{path: filepath.Join("mipmap-xhdpi", "ic_launcher.png"), size: 96},
|
||||||
{path: filepath.Join("mipmap-xxhdpi", "ic_launcher.png"), size: 144},
|
{path: filepath.Join("mipmap-xxhdpi", "ic_launcher.png"), size: 144},
|
||||||
@@ -394,12 +393,16 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
if bi.minsdk > targetSDK {
|
if bi.minsdk > targetSDK {
|
||||||
targetSDK = bi.minsdk
|
targetSDK = bi.minsdk
|
||||||
}
|
}
|
||||||
|
minSDK := 16
|
||||||
|
if bi.minsdk > minSDK {
|
||||||
|
minSDK = bi.minsdk
|
||||||
|
}
|
||||||
permissions, features := getPermissions(perms)
|
permissions, features := getPermissions(perms)
|
||||||
appName := strings.Title(bi.name)
|
appName := strings.Title(bi.name)
|
||||||
manifestSrc := manifestData{
|
manifestSrc := manifestData{
|
||||||
AppID: bi.appID,
|
AppID: bi.appID,
|
||||||
Version: bi.version,
|
Version: bi.version,
|
||||||
MinSDK: bi.minsdk,
|
MinSDK: minSDK,
|
||||||
TargetSDK: targetSDK,
|
TargetSDK: targetSDK,
|
||||||
Permissions: permissions,
|
Permissions: permissions,
|
||||||
Features: features,
|
Features: features,
|
||||||
|
|||||||
+35
-20
@@ -3,22 +3,26 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type buildInfo struct {
|
type buildInfo struct {
|
||||||
appID string
|
appID string
|
||||||
archs []string
|
archs []string
|
||||||
ldflags string
|
ldflags string
|
||||||
minsdk int
|
minsdk int
|
||||||
name string
|
name string
|
||||||
pkgDir string
|
pkgDir string
|
||||||
pkgPath string
|
pkgPath string
|
||||||
tags string
|
iconPath string
|
||||||
target string
|
tags string
|
||||||
version int
|
target string
|
||||||
|
version int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
||||||
@@ -27,17 +31,22 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
appID := getAppID(pkgMetadata)
|
appID := getAppID(pkgMetadata)
|
||||||
|
appIcon := filepath.Join(pkgMetadata.Dir, "appicon.png")
|
||||||
|
if *iconPath != "" {
|
||||||
|
appIcon = *iconPath
|
||||||
|
}
|
||||||
bi := &buildInfo{
|
bi := &buildInfo{
|
||||||
appID: appID,
|
appID: appID,
|
||||||
archs: getArchs(),
|
archs: getArchs(),
|
||||||
ldflags: getLdFlags(appID),
|
ldflags: getLdFlags(appID),
|
||||||
minsdk: *minsdk,
|
minsdk: *minsdk,
|
||||||
name: getPkgName(pkgMetadata),
|
name: getPkgName(pkgMetadata),
|
||||||
pkgDir: pkgMetadata.Dir,
|
pkgDir: pkgMetadata.Dir,
|
||||||
pkgPath: pkgPath,
|
pkgPath: pkgPath,
|
||||||
tags: *extraTags,
|
iconPath: appIcon,
|
||||||
target: *target,
|
tags: *extraTags,
|
||||||
version: *version,
|
target: *target,
|
||||||
|
version: *version,
|
||||||
}
|
}
|
||||||
return bi, nil
|
return bi, nil
|
||||||
}
|
}
|
||||||
@@ -54,6 +63,12 @@ func getArchs() []string {
|
|||||||
return []string{"arm64", "amd64"}
|
return []string{"arm64", "amd64"}
|
||||||
case "android":
|
case "android":
|
||||||
return []string{"arm", "arm64", "386", "amd64"}
|
return []string{"arm", "arm64", "386", "amd64"}
|
||||||
|
case "windows":
|
||||||
|
goarch := os.Getenv("GOARCH")
|
||||||
|
if goarch == "" {
|
||||||
|
goarch = runtime.GOARCH
|
||||||
|
}
|
||||||
|
return []string{goarch}
|
||||||
default:
|
default:
|
||||||
// TODO: Add flag tests.
|
// TODO: Add flag tests.
|
||||||
panic("The target value has already been validated, this will never execute.")
|
panic("The target value has already been validated, this will never execute.")
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ component of the 1.0.X version for iOS and tvOS.
|
|||||||
For Android builds the -minsdk flag specify the minimum SDK level. For example,
|
For Android builds the -minsdk flag specify the minimum SDK level. For example,
|
||||||
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
|
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
|
||||||
|
|
||||||
|
For Windows builds the -minsdk flag specify the minimum OS version. For example,
|
||||||
|
use -mindk 10 to target Windows 10 only, -minsdk 6 for Windows Vista and later.
|
||||||
|
|
||||||
The -work flag prints the path to the working directory and suppress
|
The -work flag prints the path to the working directory and suppress
|
||||||
its deletion.
|
its deletion.
|
||||||
|
|
||||||
|
|||||||
@@ -222,12 +222,8 @@ int main(int argc, char * argv[]) {
|
|||||||
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
icon := *iconPath
|
if _, err := os.Stat(bi.iconPath); err == nil {
|
||||||
if icon == "" {
|
assetPlist, err := iosIcons(bi, tmpDir, app, bi.iconPath)
|
||||||
icon = filepath.Join(bi.pkgDir, "appicon.png")
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(icon); err == nil {
|
|
||||||
assetPlist, err := iosIcons(bi, tmpDir, app, icon)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-10
@@ -24,7 +24,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
target = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
|
target = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
|
||||||
archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
|
archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
|
||||||
minsdk = flag.Int("minsdk", 16, "specify minimum supported Android platform sdk version (e.g. 28 for android28 a.k.a. Android 9 Pie).")
|
minsdk = flag.Int("minsdk", 0, "specify the minimum supported operating system level")
|
||||||
buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
|
buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
|
||||||
destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
|
destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
|
||||||
appID = flag.String("appid", "", "app identifier (for -buildmode=exe)")
|
appID = flag.String("appid", "", "app identifier (for -buildmode=exe)")
|
||||||
@@ -67,7 +67,7 @@ func flagValidate() error {
|
|||||||
return errors.New("please specify -target")
|
return errors.New("please specify -target")
|
||||||
}
|
}
|
||||||
switch *target {
|
switch *target {
|
||||||
case "ios", "tvos", "android", "js":
|
case "ios", "tvos", "android", "js", "windows":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid -target %s", *target)
|
return fmt.Errorf("invalid -target %s", *target)
|
||||||
}
|
}
|
||||||
@@ -96,6 +96,8 @@ func build(bi *buildInfo) error {
|
|||||||
return buildIOS(tmpDir, *target, bi)
|
return buildIOS(tmpDir, *target, bi)
|
||||||
case "android":
|
case "android":
|
||||||
return buildAndroid(tmpDir, bi)
|
return buildAndroid(tmpDir, bi)
|
||||||
|
case "windows":
|
||||||
|
return buildWindows(tmpDir, bi)
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@@ -188,13 +190,6 @@ func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
|||||||
for _, v := range variants {
|
for _, v := range variants {
|
||||||
v := v
|
v := v
|
||||||
resizes.Go(func() (err error) {
|
resizes.Go(func() (err error) {
|
||||||
scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
|
|
||||||
op := draw.Src
|
|
||||||
if v.fill {
|
|
||||||
op = draw.Over
|
|
||||||
draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
|
|
||||||
}
|
|
||||||
draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil)
|
|
||||||
path := filepath.Join(baseDir, v.path)
|
path := filepath.Join(baseDir, v.path)
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -208,8 +203,20 @@ func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
|||||||
err = cerr
|
err = cerr
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return png.Encode(f, scaled)
|
return png.Encode(f, resizeIcon(v, img))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return resizes.Wait()
|
return resizes.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resizeIcon(v iconVariant, img image.Image) *image.NRGBA {
|
||||||
|
scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
|
||||||
|
op := draw.Src
|
||||||
|
if v.fill {
|
||||||
|
op = draw.Over
|
||||||
|
draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
|
||||||
|
}
|
||||||
|
draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil)
|
||||||
|
|
||||||
|
return scaled
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildWindows(tmpDir string, bi *buildInfo) error {
|
||||||
|
builder := &windowsBuilder{TempDir: tmpDir, BuildInfo: bi}
|
||||||
|
builder.DestDir = *destPath
|
||||||
|
if builder.DestDir == "" {
|
||||||
|
builder.DestDir = bi.pkgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
name := bi.name
|
||||||
|
if *destPath != "" {
|
||||||
|
if filepath.Ext(*destPath) != ".exe" {
|
||||||
|
return fmt.Errorf("invalid output name %q, it must end with `.exe`", *destPath)
|
||||||
|
}
|
||||||
|
name = filepath.Base(*destPath)
|
||||||
|
}
|
||||||
|
name = strings.TrimSuffix(name, ".exe")
|
||||||
|
sdk := bi.minsdk
|
||||||
|
if sdk > 10 {
|
||||||
|
return fmt.Errorf("invalid minsdk (%d) it's higher than Windows 10", sdk)
|
||||||
|
}
|
||||||
|
version := strconv.Itoa(bi.version)
|
||||||
|
if bi.version > math.MaxUint16 {
|
||||||
|
return fmt.Errorf("version (%d) is larger than the maximum (%d)", bi.version, math.MaxUint16)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Resources.Name = name
|
||||||
|
builder.Manifest.Name = name
|
||||||
|
builder.Manifest.WindowsVersion = sdk
|
||||||
|
builder.Resources.Version = "1,0,0," + version
|
||||||
|
builder.Manifest.Version = "1.0.0." + version
|
||||||
|
|
||||||
|
if err := builder.createIcon(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.createManifest(); err != nil {
|
||||||
|
return fmt.Errorf("can't create manifest: %v", err)
|
||||||
|
}
|
||||||
|
if err := builder.createResource(); err != nil {
|
||||||
|
return fmt.Errorf("can't create resource: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.buildResource(); err != nil {
|
||||||
|
return fmt.Errorf("can't build the resources: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arch := range builder.BuildInfo.archs {
|
||||||
|
if err := builder.buildProgram(arch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
windowsResources struct {
|
||||||
|
IconPath string
|
||||||
|
ManifestPath string
|
||||||
|
Version string
|
||||||
|
Name string
|
||||||
|
CompanyName string
|
||||||
|
}
|
||||||
|
windowsManifest struct {
|
||||||
|
Version string
|
||||||
|
WindowsVersion int
|
||||||
|
Name string
|
||||||
|
Arch string
|
||||||
|
}
|
||||||
|
windowsFiles struct {
|
||||||
|
Resources windowsResources
|
||||||
|
ResourcesPath string
|
||||||
|
Manifest windowsManifest
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type windowsBuilder struct {
|
||||||
|
TempDir string
|
||||||
|
DestDir string
|
||||||
|
BuildInfo *buildInfo
|
||||||
|
windowsFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *windowsBuilder) createIcon() (err error) {
|
||||||
|
if _, err := os.Stat(b.BuildInfo.iconPath); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iconFile, err := os.Open(b.BuildInfo.iconPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't read the icon located at %s: %v", b.BuildInfo.iconPath, err)
|
||||||
|
}
|
||||||
|
defer iconFile.Close()
|
||||||
|
|
||||||
|
iconImage, err := png.Decode(iconFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't decode the PNG file (%s): %v", b.BuildInfo.iconPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Resources.IconPath = filepath.Join(b.TempDir, "appicon.ico")
|
||||||
|
exeIcon, err := os.Create(b.Resources.IconPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("impossibe to create icon file at %s: %v", b.Resources.IconPath, err)
|
||||||
|
}
|
||||||
|
defer exeIcon.Close()
|
||||||
|
|
||||||
|
return convertPNGtoICO(exeIcon, iconImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPNGtoICO(w io.Writer, img image.Image) error {
|
||||||
|
// The file must be in .ICO format.
|
||||||
|
const (
|
||||||
|
OffsetICONDIR int = 2 * 3
|
||||||
|
OffsetICONDIRENTRY int = (4 * 1) + (2 * 2) + (4 * 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
sizes := []int{16, 32, 48, 64, 128, 256}
|
||||||
|
|
||||||
|
// ICONDIR structure
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, [3]uint16{0, 1, uint16(len(sizes))}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
headerOffset = OffsetICONDIR + (OffsetICONDIRENTRY * len(sizes))
|
||||||
|
imageBuffer bytes.Buffer
|
||||||
|
)
|
||||||
|
for _, size := range sizes {
|
||||||
|
imageOffset := imageBuffer.Len()
|
||||||
|
scaledImage := resizeIcon(iconVariant{size: size, fill: false}, img)
|
||||||
|
|
||||||
|
if err := png.Encode(&imageBuffer, scaledImage); err != nil {
|
||||||
|
return fmt.Errorf("can't encode image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ICONDIRENTRY 0-3 structure.
|
||||||
|
// The width/height is defined from 0 to 255 (uint8). But "0" means 256px.
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, [4]uint8{uint8(size % 256), uint8(size % 256), 0, 0}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// ICONDIRENTRY 4-6 structure
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, [2]uint16{1, 32}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// ICONDIRENTRY 8-12 structure
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, [2]uint32{uint32(imageBuffer.Len() - imageOffset), uint32(headerOffset + imageOffset)}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := io.Copy(w, &imageBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *windowsBuilder) createManifest() error {
|
||||||
|
// The manifest have some information about the executable itself,
|
||||||
|
// such as the supported Windows and Execution Level/Permissions.
|
||||||
|
b.Resources.ManifestPath = filepath.Join(b.TempDir, "manifest_windows.xml")
|
||||||
|
manifest, err := os.Create(b.Resources.ManifestPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer manifest.Close()
|
||||||
|
|
||||||
|
return b.Manifest.encode(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *windowsBuilder) createResource() error {
|
||||||
|
// The resource includes the icon and manifest previously created
|
||||||
|
// it also defines the version and some other information about the
|
||||||
|
// program and the developer.
|
||||||
|
b.ResourcesPath = filepath.Join(b.TempDir, "main_windows.rc")
|
||||||
|
resources, err := os.Create(b.ResourcesPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resources.Close()
|
||||||
|
|
||||||
|
return b.Resources.encode(resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *windowsBuilder) buildResource() error {
|
||||||
|
cmd := exec.Command(
|
||||||
|
"windres",
|
||||||
|
b.ResourcesPath,
|
||||||
|
filepath.Join(b.BuildInfo.pkgPath, "main_windows.syso"),
|
||||||
|
)
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *windowsBuilder) buildProgram(arch string) error {
|
||||||
|
dest := b.DestDir
|
||||||
|
if len(b.BuildInfo.archs) > 1 {
|
||||||
|
dest = filepath.Join(filepath.Dir(b.DestDir), b.Resources.Name+"_"+arch+".exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"go",
|
||||||
|
"build",
|
||||||
|
"-ldflags=-H=windowsgui "+b.BuildInfo.ldflags,
|
||||||
|
"-tags="+b.BuildInfo.tags,
|
||||||
|
"-o", dest,
|
||||||
|
b.BuildInfo.pkgPath,
|
||||||
|
)
|
||||||
|
cmd.Env = append(
|
||||||
|
os.Environ(),
|
||||||
|
"GOOS=windows",
|
||||||
|
"GOARCH="+arch,
|
||||||
|
)
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *windowsManifest) encode(w io.Writer) error {
|
||||||
|
t := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="{{.Name}}" version="{{.Version}}" />
|
||||||
|
<description>{{.Name}}</description>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
{{if (le .WindowsVersion 10)}}<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
{{end}}
|
||||||
|
{{if (le .WindowsVersion 9)}}<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
{{end}}
|
||||||
|
{{if (le .WindowsVersion 8)}}<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
{{end}}
|
||||||
|
{{if (le .WindowsVersion 7)}}<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
{{end}}
|
||||||
|
{{if (le .WindowsVersion 6)}}<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
|
{{end}}
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>`
|
||||||
|
template, err := template.New("manifest").Parse(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.Execute(w, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *windowsResources) encode(w io.Writer) error {
|
||||||
|
const t = `{{if .IconPath}}#define IDI_ICON1 1
|
||||||
|
IDI_ICON1 ICON "{{escapePath .IconPath}}"{{end}}
|
||||||
|
|
||||||
|
#define IDI_MANIFEST 1
|
||||||
|
IDI_MANIFEST 24 "{{escapePath .ManifestPath}}"
|
||||||
|
|
||||||
|
#define IDI_VERSION 1
|
||||||
|
IDI_VERSION VERSIONINFO
|
||||||
|
FILEVERSION {{.Version}}
|
||||||
|
PRODUCTVERSION {{.Version}}
|
||||||
|
FILEFLAGSMASK 0X3FL
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
FILEOS 0X40004L
|
||||||
|
FILETYPE 0X1L
|
||||||
|
FILESUBTYPE 0x0L
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "04000400"
|
||||||
|
BEGIN
|
||||||
|
VALUE "ProductVersion", "{{.Version}}"
|
||||||
|
VALUE "FileVersion", "{{.Version}}"
|
||||||
|
VALUE "FileDescription", "{{.Name}}"
|
||||||
|
VALUE "ProductName", "{{.Name}}"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x0400, 0x0400
|
||||||
|
END
|
||||||
|
END`
|
||||||
|
template, err := template.New("rc").Funcs(template.FuncMap{"escapePath": func(s string) string {
|
||||||
|
return strings.Replace(s, `\`, `\\`, -1)
|
||||||
|
}}).Parse(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.Execute(w, f)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user