Support for upx and more go:embed.

This commit is contained in:
Lea Anthony 2021-05-18 21:25:16 +10:00
commit 08f4476087
14 changed files with 172 additions and 103 deletions

View file

@ -0,0 +1,54 @@
# Build
The build command processes the Wails project and generates an application binary.
## Usage
`wails build <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -clean | Clean the bin directory before building | |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -o path/to/binary | Compile to given path/filename | |
| -k | Keep generated assets | |
| -package | Create a platform specific package | |
| -production | Compile in production mode: -ldflags="-w -s" + "-h windows" on Windows | |
| -tags | Build tags to pass to Go compiler (quoted and space separated) | |
| -upx | Compress final binary with UPX (if installed) | |
| -upxflags "custom flags" | Flags to pass to upx | |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
## The Build Process
The build process is as follows:
- The flags are processed, and an Options struct built containing the build context.
- The type of target is determined, and a custom build process is followed for target.
### Desktop Target
- The frontend dependencies are installed. The command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The frontend is then built. This command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The project directory is checked to see if the `build` directory exists. If not, it is created and default project assets are copied to it.
- An asset bundle is then created by reading the `html` key from `wails.json` and loading the referenced file. This is then parsed, looking for local Javascript and CSS references. Those files are in turn loaded into memory, converted to C data and saved into the asset bundle located at `build/assets.h`, which also includes the original HTML.
- The application icon is then processed: if there is no `build/appicon.png`, a default icon is copied. On Windows, an `app.ico` file is generated from this png. On Mac, `icons.icns` is generated.
- If there are icons in the `build/tray` directory, these are processed, converted to C data and saved as `build/trayicons.h`, ready for the compilation step.
- If there are icons in the `build/dialog` directory, these are processed, converted to C data and saved as `build/userdialogicons.h`, ready for the compilation step.
- If the `-package` flag is given for a Windows target, the Windows assets in the `build/windows` directory are processed: manifest + icons compiled to a `.syso` file (deleted after compilation).
- If we are building a universal binary for Mac, the application is compiled for both `arm64` and `amd64`. The `lipo` tool is then executed to create the universal binary.
- If we are not building a universal binary for Mac, the application is built using `go build`, using build tags to indicate type of application and build mode (debug/production).
- If the `-upx` flag was provided, `upx` is invoked to compress the binary. Custom flags may be provided using the `-upxflags` flag.
- If the `package` flag is given for a non Windows target, the application is bundled for the platform. On Mac, this creates a `.app` with the processed icons, the `Info.plist` in `build/darwin` and the compiled binary.
### Server Target
TBD
### Hybrid Target
TBD

View file

@ -36,7 +36,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
compress := false
command.BoolFlag("compress", "Compress final binary", &compress)
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
compressFlags := ""
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
// Setup Platform flag
platform := runtime.GOOS
@ -131,6 +134,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
KeepAssets: keepAssets,
Verbosity: verbosity,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
}

View file

@ -0,0 +1,22 @@
# Dev
The dev command allows you to develop your application through a standard browser.
## Usage
`wails dev <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -e list,of,extensions | File extensions to trigger rebuilds | go |
| -w | Show warnings | false |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
| -loglevel | Loglevel to pass to the application - Trace, Debug, Info, Warning, Error | Debug |
## How it works
The project is build

View file

@ -58,6 +58,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
@ -90,13 +94,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
debounceQuit := make(chan bool, 1)
var passthruArgs []string
if len(os.Args) > 2 {
passthruArgs = os.Args[2:]
}
//if len(os.Args) > 2 {
// passthruArgs = os.Args[2:]
//}
// Do initial build
logger.Println("Building application for development...")
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs)
newProcess, appBinary, err := restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
if newProcess != nil {
debugBinaryProcess = newProcess
}
@ -104,6 +108,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if err != nil {
return err
}
var newBinaryProcess *process.Process
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
@ -147,7 +152,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Do a rebuild
// Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs)
newBinaryProcess, _, err = restartApp(logger, ldflags, compilerCommand, debugBinaryProcess, loglevel, passthruArgs, verbosity)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
@ -209,6 +214,12 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
}
// Remove dev binary
err = os.Remove(appBinary)
if err != nil {
return err
}
LogGreen("Development mode exited")
return nil
@ -236,14 +247,14 @@ exit:
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string) (*process.Process, error) {
func restartApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string, passthruArgs []string, verbosity int) (*process.Process, string, error) {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
appBinary, err := buildApp(logger, ldflags, compilerCommand, verbosity)
println()
if err != nil {
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, nil
return nil, "", nil
}
// Kill existing binary if need be
@ -275,18 +286,21 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, nil
return newProcess, appBinary, nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
func buildApp(logger *clilogger.CLILogger, ldflags string, compilerCommand string, verbosity int) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
outputFile := "wailsdev"
if runtime.GOOS == "windows" {
outputFile += ".exe"
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
@ -294,6 +308,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: true,
Verbosity: verbosity,
}
return build.Build(buildOptions)

View file

@ -0,0 +1,12 @@
package assets
import _ "embed"
//go:embed desktop_darwin.js
var desktopDarwinJS string
//go:embed desktop_windows.js
var desktopWindowsJS string
//go:embed wails.js
var wailsJS string

View file

@ -1,9 +1,10 @@
# Assets Directory
# Build Directory
The assets directory is used to house all the assets of your application.
The build directory is used to house all the build files and assets for your application.
The structure is:
* bin - Output directory
* dialog - Icons for dialogs
* tray - Icons for the system tray
* mac - MacOS specific files

View file

@ -74,3 +74,11 @@ func RegenerateManifest(target string) error {
}
return a.CopyFile("windows/wails.exe.manifest", target, 0644)
}
func RegenerateAppIcon(target string) error {
a, err := debme.FS(assets, "build")
if err != nil {
return err
}
return a.CopyFile("appicon.png", target, 0644)
}

View file

@ -337,16 +337,23 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
return nil
}
if verbose {
println("upx", "--best", "--no-color", "--no-progress", options.CompiledBinary)
var args = []string{"--best", "--no-color", "--no-progress", options.CompiledBinary}
if options.CompressFlags != "" {
args = strings.Split(options.CompressFlags, " ")
args = append(args, options.CompiledBinary)
}
output, err := exec.Command("upx", "--best", "--no-color", "--no-progress", options.CompiledBinary).Output()
if verbose {
println("upx", strings.Join(args, " "))
}
output, err := exec.Command("upx", args...).Output()
if err != nil {
return errors.Wrap(err, "Error during compression:")
}
if verbose {
println(output)
println(string(output))
}
return nil
}

View file

@ -47,6 +47,7 @@ type Options struct {
KeepAssets bool // Keep the generated assets/files
Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
Compress bool // Compress the final binary
CompressFlags string // Flags to pass to UPX
AppleIdentity string
}

View file

@ -2,6 +2,7 @@ package build
import (
"fmt"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"io/ioutil"
"path/filepath"
@ -27,9 +28,7 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
// Check assets directory exists
if !fs.DirExists(options.ProjectData.BuildDir) {
// Path to default assets
defaultAssets := fs.RelativePath("./internal/assets")
// Copy the default assets directory
err := fs.CopyDir(defaultAssets, options.ProjectData.BuildDir)
err := buildassets.Install(options.ProjectData.Path, options.ProjectData.Name)
if err != nil {
return err
}
@ -108,7 +107,7 @@ func (d *DesktopBuilder) processApplicationIcon(assetDir string) error {
// Copy default icon if one doesn't exist
iconFile := filepath.Join(d.projectData.BuildDir, "appicon.png")
if !fs.FileExists(iconFile) {
err := fs.CopyFile(defaultIconPath(), iconFile)
err := buildassets.RegenerateAppIcon(iconFile)
if err != nil {
return err
}

View file

@ -1,52 +0,0 @@
# Assets Directory
The assets directory is used to house all the assets of your application.
The structure is:
* dialog - Icons for dialogs
* tray - Icons for the system tray
* mac - MacOS specific files
* linux - Linux specific files
* windows - Windows specific files
## Dialog Icons
Place any PNG file in this directory to be able to use them in message dialogs.
The files should have names in the following format: `name[-(light|dark)][2x].png`
Examples:
* `mypic.png` - Standard definition icon with ID `mypic`
* `mypic-light.png` - Standard definition icon with ID `mypic`, used when system theme is light
* `mypic-dark.png` - Standard definition icon with ID `mypic`, used when system theme is dark
* `mypic2x.png` - High definition icon with ID `mypic`
* `mypic-light2x.png` - High definition icon with ID `mypic`, used when system theme is light
* `mypic-dark2x.png` - High definition icon with ID `mypic`, used when system theme is dark
### Order of preference
Icons are selected with the following order of preference:
For High Definition displays:
* name-(theme)2x.png
* name2x.png
* name-(theme).png
* name.png
For Standard Definition displays:
* name-(theme).png
* name.png
## Tray
Place any PNG file in this directory to be able to use them as tray icons.
The name of the filename will be the ID to reference the image.
Example:
* `mypic.png` - May be referenced using `runtime.Tray.SetIcon("mypic")`
## Mac
The `mac` directory holds files specific to Mac builds, such as `Info.plist`. These may be edited and used as part of the build.

View file

@ -61,11 +61,6 @@ func getBuildBaseDirectory(options *Options) (string, error) {
return buildDirectory, nil
}
// Gets the path to the default icon
func defaultIconPath() string {
return fs.RelativePath("internal/packager/icon1024.png")
}
// Gets the platform dependent package assets directory
func getPackageAssetsDirectory() string {
return fs.RelativePath("internal/packager", runtime.GOOS)

View file

@ -3,6 +3,7 @@ package build
import (
"bytes"
"fmt"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"image"
"io/ioutil"
"os"
@ -149,8 +150,7 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
// Install default icon if one doesn't exist
if !fs.FileExists(appIcon) {
// No - Install default icon
defaultIcon := fs.RelativePath("./internal/packager/icon1024.png")
err = fs.CopyFile(defaultIcon, appIcon)
err := buildassets.RegenerateAppIcon(appIcon)
if err != nil {
return
}

View file

@ -2,6 +2,7 @@ package parser
import (
"bytes"
_ "embed"
"io/ioutil"
"os"
"path/filepath"
@ -11,6 +12,24 @@ import (
"github.com/wailsapp/wails/v2/internal/fs"
)
//go:embed index.template
var indexTemplate string
//go:embed index.d.template
var indexDTemplate string
//go:embed package.template
var packageTemplate string
//go:embed package.d.template
var packageDTemplate string
//go:embed globals.d.template
var globalsDTemplate string
//go:embed package.json
var packageJSON string
// GenerateWailsFrontendPackage will generate a Javascript/Typescript
// package in `<project>/frontend/wails` that defines which methods
// and structs are bound to your frontend
@ -51,9 +70,8 @@ func (p *Parser) generateModule() error {
}
// Copy the standard files
srcFile := fs.RelativePath("./package.json")
tgtFile := filepath.Join(moduleDir, "package.json")
err = fs.CopyFile(srcFile, tgtFile)
err = fs.CopyFile(packageJSON, tgtFile)
if err != nil {
return err
}
@ -102,11 +120,8 @@ func createBackendJSDirectory() (string, error) {
func generatePackage(pkg *Package, moduledir string) error {
// Get path to local file
typescriptTemplateFile := fs.RelativePath("./package.d.template")
// Load typescript template
typescriptTemplateData := fs.MustLoadString(typescriptTemplateFile)
typescriptTemplateData := fs.MustLoadString(packageDTemplate)
typescriptTemplate, err := template.New("typescript").Parse(typescriptTemplateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
@ -125,11 +140,8 @@ func generatePackage(pkg *Package, moduledir string) error {
return errors.Wrap(err, "Error writing backend package file")
}
// Get path to local file
javascriptTemplateFile := fs.RelativePath("./package.template")
// Load javascript template
javascriptTemplateData := fs.MustLoadString(javascriptTemplateFile)
javascriptTemplateData := fs.MustLoadString(packageTemplate)
javascriptTemplate, err := template.New("javascript").Parse(javascriptTemplateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
@ -154,11 +166,8 @@ func generatePackage(pkg *Package, moduledir string) error {
func generateIndexJS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./index.template")
// Load template
templateData := fs.MustLoadString(templateFile)
templateData := fs.MustLoadString(indexTemplate)
packagesTemplate, err := template.New("index").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
@ -183,11 +192,8 @@ func generateIndexJS(dir string, packages []*Package) error {
}
func generateIndexTS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./index.d.template")
// Load template
templateData := fs.MustLoadString(templateFile)
templateData := fs.MustLoadString(indexDTemplate)
indexTSTemplate, err := template.New("index.d").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
@ -213,11 +219,8 @@ func generateIndexTS(dir string, packages []*Package) error {
func generateGlobalsTS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./globals.d.template")
// Load template
templateData := fs.MustLoadString(templateFile)
templateData := fs.MustLoadString(globalsDTemplate)
packagesTemplate, err := template.New("globals").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")