Support building arm64 & universal binaries.

This commit is contained in:
Lea Anthony
2021-03-25 20:55:59 +11:00
parent 3c30924493
commit 2dd8833e67
6 changed files with 177 additions and 58 deletions

View File

@@ -3,8 +3,10 @@ package build
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/leaanthony/clir"
@@ -23,8 +25,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
command := app.NewSubCommand("build", "Builds the application")
// Setup target type flag
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
//description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
//command.StringFlag("t", description, &outputType)
// Setup production flag
production := false
@@ -41,22 +43,26 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
platform := runtime.GOOS
command.StringFlag("platform", "Platform to target", &platform)
// Quiet Build
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// Log to file
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
//logFile := ""
//command.StringFlag("l", "Log to file", &logFile)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
// Retain assets
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
appleIdentity := ""
if runtime.GOOS == "darwin" {
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
@@ -64,6 +70,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
command.Action(func() error {
quiet := verbosity == 0
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
@@ -82,29 +90,72 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
return fmt.Errorf("must use `-package` flag when using `-sign`")
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Setup mode
mode := build.Debug
if production {
mode = build.Production
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
//"linux/amd64",
//"linux/arm-7",
//"windows/amd64",
})
if !validPlatformArch.Contains(platform) {
return fmt.Errorf("platform %s is not supported", platform)
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
AppleIdentity: appleIdentity,
Verbosity: verbosity,
}
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = runtime.GOARCH
if len(platformSplit) == 2 {
buildOptions.Arch = platformSplit[1]
}
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
buildModeText := "debug"
if production {
buildModeText = "production"
}
// Write out the system information
fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
fmt.Fprintf(w, "Compiler: \t%s\n", buildOptions.Compiler)
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
if len(buildOptions.OutputFile) > 0 {
fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
fmt.Fprintf(w, "\n")
w.Flush()
return doBuild(buildOptions)
})
}

View File

@@ -19,6 +19,10 @@ import (
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
const (
VERBOSE int = 2
)
// BaseBuilder is the common builder struct
type BaseBuilder struct {
filesToDelete slicer.StringSlicer
@@ -142,11 +146,23 @@ func (b *BaseBuilder) CleanUp() {
})
}
func (b *BaseBuilder) OutputFilename(options *Options) string {
outputFile := options.OutputFile
if outputFile == "" {
outputFile = b.projectData.OutputFilename
}
return outputFile
}
// CompileProject compiles the project
func (b *BaseBuilder) CompileProject(options *Options) error {
// Run go mod tidy first
cmd := exec.Command(options.Compiler, "mod", "tidy")
if options.Verbosity == VERBOSE {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
err := cmd.Run()
if err != nil {
return err
@@ -167,6 +183,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
// potentially try and see if the assets have changed but will
// this take as much time as a `-a` build?
commands.Add("-a")
commands.Add("-x")
var tags slicer.StringSlicer
tags.Add(options.OutputType)
@@ -195,10 +212,10 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
// Get application build directory
appDir := options.BuildDirectory
err = cleanBuildDirectory(options)
if err != nil {
return err
}
//err = cleanBuildDirectory(options)
//if err != nil {
// return err
//}
if options.LDFlags != "" {
commands.Add("-ldflags")
@@ -206,10 +223,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
// Set up output filename
outputFile := options.OutputFile
if outputFile == "" {
outputFile = b.projectData.OutputFilename
}
outputFile := b.OutputFilename(options)
compiledBinary := filepath.Join(appDir, outputFile)
commands.Add("-o")
commands.Add(compiledBinary)
@@ -219,7 +233,10 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
// Create the command
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
if options.Verbosity == VERBOSE {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
// Set the directory
cmd.Dir = b.projectData.Path
@@ -241,29 +258,36 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
return v
})
// Setup buffers
var stdo, stde bytes.Buffer
cmd.Stdout = &stdo
cmd.Stderr = &stde
cmd.Env = upsertEnv(cmd.Env, "GOOS", func(v string) string {
return options.Platform
})
cmd.Env = upsertEnv(cmd.Env, "GOARCH", func(v string) string {
return options.Arch
})
cmd.Env = upsertEnv(cmd.Env, "CGO_ENABLED", func(v string) string {
return "1"
})
// Run command
err = cmd.Run()
// Format error if we have one
if err != nil {
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
return err
}
return nil
}
// NpmInstall runs "npm install" in the given directory
func (b *BaseBuilder) NpmInstall(sourceDir string) error {
return b.NpmInstallUsingCommand(sourceDir, "npm install")
func (b *BaseBuilder) NpmInstall(sourceDir string, verbose bool) error {
return b.NpmInstallUsingCommand(sourceDir, "npm install", verbose)
}
// NpmInstallUsingCommand runs the given install command in the specified npm project directory
func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string) error {
func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string, verbose bool) error {
packageJSON := filepath.Join(sourceDir, "package.json")
@@ -305,7 +329,7 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st
// Split up the InstallCommand and execute it
cmd := strings.Split(installCommand, " ")
stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...)
if err != nil {
if verbose || err != nil {
for _, l := range strings.Split(stdout, "\n") {
fmt.Printf(" %s\n", l)
}
@@ -354,31 +378,40 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
// TODO: Fix this up from the CLI
verbose := false
verbose := b.options.Verbosity == VERBOSE
frontendDir := filepath.Join(b.projectData.Path, "frontend")
// Check there is an 'InstallCommand' provided in wails.json
if b.projectData.InstallCommand == "" {
// No - don't install
outputLogger.Println(" - No Install command. Skipping.")
outputLogger.Println("No Install command. Skipping.")
} else {
// Do install if needed
outputLogger.Println(" - Installing dependencies...")
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
outputLogger.Print("Installing frontend dependencies: ")
if verbose {
outputLogger.Println("")
outputLogger.Println("\tCommand: " + b.projectData.InstallCommand)
}
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand, verbose); err != nil {
return err
}
outputLogger.Println("Done.")
}
// Check if there is a build command
if b.projectData.BuildCommand == "" {
outputLogger.Println(" - No Build command. Skipping.")
outputLogger.Println("No Build command. Skipping.")
// No - ignore
return nil
}
outputLogger.Println(" - Compiling Frontend Project")
outputLogger.Print("Compiling frontend: ")
cmd := strings.Split(b.projectData.BuildCommand, " ")
if verbose {
outputLogger.Println("")
outputLogger.Println("\tCommand: '" + strings.Join(cmd, " ") + "'")
}
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil {
for _, l := range strings.Split(stdout, "\n") {
@@ -388,7 +421,12 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
fmt.Printf(" %s\n", l)
}
}
return err
if err != nil {
return err
}
outputLogger.Println("Done.")
return nil
}
// ExtractAssets gets the assets from the index.html file

View File

@@ -6,7 +6,10 @@ import (
"path/filepath"
"runtime"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
@@ -32,12 +35,14 @@ type Options struct {
ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building
Platform string // The platform to build for
Arch string // The architecture to build for
Compiler string // The compiler command to use
IgnoreFrontend bool // Indicates if the frontend does not need building
OutputFile string // Override the output filename
BuildDirectory string // Directory to use for building the application
CompiledBinary string // Fully qualified path to the compiled binary
KeepAssets bool // /Keep the generated assets/files
Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
AppleIdentity string
}
@@ -58,12 +63,6 @@ func Build(options *Options) (string, error) {
return "", err
}
// Check platform
validPlatforms := slicer.String([]string{"linux", "darwin", "windows"})
if !validPlatforms.Contains(options.Platform) {
return "", fmt.Errorf("platform %s is not supported", options.Platform)
}
// Load project
projectData, err := project.Load(cwd)
if err != nil {
@@ -71,7 +70,7 @@ func Build(options *Options) (string, error) {
}
options.ProjectData = projectData
// Calculate build dir
// Set build directory
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
// Save the project type
@@ -107,7 +106,6 @@ func Build(options *Options) (string, error) {
// return "", err
// }
if !options.IgnoreFrontend {
outputLogger.Println(" - Building Project Frontend")
err = builder.BuildFrontend(outputLogger)
if err != nil {
return "", err
@@ -115,30 +113,61 @@ func Build(options *Options) (string, error) {
}
// Build the base assets
outputLogger.Println(" - Compiling Assets")
err = builder.BuildAssets(options)
if err != nil {
return "", err
}
// Compile the application
outputLogger.Print(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
err = builder.CompileProject(options)
if err != nil {
return "", err
outputLogger.Print("Compiling application: ")
if options.Platform == "darwin" && options.Arch == "universal" {
outputFile := builder.OutputFilename(options)
amd64Filename := outputFile + "-amd64"
arm64Filename := outputFile + "-arm64"
// Build amd64 first
options.Arch = "amd64"
options.OutputFile = amd64Filename
err = builder.CompileProject(options)
if err != nil {
return "", err
}
// Build arm64
options.Arch = "arm64"
options.OutputFile = arm64Filename
err = builder.CompileProject(options)
if err != nil {
return "", err
}
// Run lipo
_, stderr, err := shell.RunCommand(options.BuildDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
if err != nil {
return "", fmt.Errorf("%s - %s", err.Error(), stderr)
}
// Remove temp binaries
fs.DeleteFile(filepath.Join(options.BuildDirectory, amd64Filename))
fs.DeleteFile(filepath.Join(options.BuildDirectory, arm64Filename))
projectData.OutputFilename = outputFile
} else {
err = builder.CompileProject(options)
if err != nil {
return "", err
}
}
outputLogger.Println("done.")
outputLogger.Println("Done.")
// Do we need to pack the app?
if options.Pack {
outputLogger.Println(" - Packaging Application")
outputLogger.Print("Packaging application: ")
// TODO: Allow cross platform build
err = packageProject(options, runtime.GOOS)
if err != nil {
return "", err
}
outputLogger.Println("Done.")
}
return projectData.OutputFilename, nil

View File

@@ -12,5 +12,6 @@ type Builder interface {
BuildFrontend(*clilogger.CLILogger) error
BuildRuntime(*Options) error
CompileProject(*Options) error
OutputFilename(*Options) string
CleanUp()
}

View File

@@ -55,7 +55,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
var err error
outputLogger := options.Logger
outputLogger.Print(" - Embedding Assets...")
outputLogger.Print("Building assets: ")
// Get target asset directory
assetDir, err := fs.RelativeToCwd("build")
@@ -96,7 +96,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
return err
}
outputLogger.Println("done.")
outputLogger.Println("Done.")
return nil
}
@@ -125,11 +125,11 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
sourceDir := fs.RelativePath("../../../internal/runtime/js")
if err := d.NpmInstall(sourceDir); err != nil {
if err := d.NpmInstall(sourceDir, options.Verbosity == VERBOSE); err != nil {
return err
}
outputLogger.Print(" - Embedding Runtime...")
outputLogger.Print("Embedding Runtime: ")
envvars := []string{"WAILSPLATFORM=" + options.Platform}
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
return err

View File

@@ -98,7 +98,7 @@ func (s *ServerBuilder) BuildBaseAssets(assets *html.AssetBundle) error {
func (s *ServerBuilder) BuildRuntime(options *Options) error {
sourceDir := fs.RelativePath("../../../internal/runtime/js")
if err := s.NpmInstall(sourceDir); err != nil {
if err := s.NpmInstall(sourceDir, options.Verbosity == VERBOSE); err != nil {
return err
}