diff --git a/v2/cmd/wails/internal/commands/build/README.md b/v2/cmd/wails/internal/commands/build/README.md new file mode 100644 index 000000000..9e52a89a8 --- /dev/null +++ b/v2/cmd/wails/internal/commands/build/README.md @@ -0,0 +1,54 @@ +# Build + +The build command processes the Wails project and generates an application binary. + +## Usage + +`wails build ` + +### 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 + diff --git a/v2/cmd/wails/internal/commands/build/build.go b/v2/cmd/wails/internal/commands/build/build.go index cb095494f..5bc79e668 100644 --- a/v2/cmd/wails/internal/commands/build/build.go +++ b/v2/cmd/wails/internal/commands/build/build.go @@ -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, } diff --git a/v2/cmd/wails/internal/commands/dev/README.md b/v2/cmd/wails/internal/commands/dev/README.md new file mode 100644 index 000000000..f51b0a4d9 --- /dev/null +++ b/v2/cmd/wails/internal/commands/dev/README.md @@ -0,0 +1,22 @@ +# Dev + +The dev command allows you to develop your application through a standard browser. + +## Usage + +`wails dev ` + +### 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 \ No newline at end of file diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go index 0c3d4190b..8859765eb 100644 --- a/v2/cmd/wails/internal/commands/dev/dev.go +++ b/v2/cmd/wails/internal/commands/dev/dev.go @@ -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) diff --git a/v2/internal/runtime/assets/assets.go b/v2/internal/runtime/assets/assets.go new file mode 100644 index 000000000..cdc0f3b2e --- /dev/null +++ b/v2/internal/runtime/assets/assets.go @@ -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 diff --git a/v2/pkg/buildassets/build/README.md b/v2/pkg/buildassets/build/README.md index b8c1271f8..379aa7008 100644 --- a/v2/pkg/buildassets/build/README.md +++ b/v2/pkg/buildassets/build/README.md @@ -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 diff --git a/v2/pkg/buildassets/buildassets.go b/v2/pkg/buildassets/buildassets.go index 3924dbbbf..bc9b6c90c 100644 --- a/v2/pkg/buildassets/buildassets.go +++ b/v2/pkg/buildassets/buildassets.go @@ -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) +} diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go index 106cdcbc6..1225c9208 100644 --- a/v2/pkg/commands/build/base.go +++ b/v2/pkg/commands/build/base.go @@ -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 } diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 96000314c..4c7c0c1f8 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -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 } diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go index 27bcbcf37..939e35d93 100644 --- a/v2/pkg/commands/build/desktop.go +++ b/v2/pkg/commands/build/desktop.go @@ -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 } diff --git a/v2/pkg/commands/build/internal/assets/README.md b/v2/pkg/commands/build/internal/assets/README.md deleted file mode 100644 index b8c1271f8..000000000 --- a/v2/pkg/commands/build/internal/assets/README.md +++ /dev/null @@ -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. diff --git a/v2/pkg/commands/build/packager.go b/v2/pkg/commands/build/packager.go index 9472d35ea..9c1f2bd3f 100644 --- a/v2/pkg/commands/build/packager.go +++ b/v2/pkg/commands/build/packager.go @@ -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) diff --git a/v2/pkg/commands/build/packager_darwin.go b/v2/pkg/commands/build/packager_darwin.go index 9668492a4..73249e20c 100644 --- a/v2/pkg/commands/build/packager_darwin.go +++ b/v2/pkg/commands/build/packager_darwin.go @@ -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 } diff --git a/v2/pkg/parser/generate.go b/v2/pkg/parser/generate.go index 66518704c..b45a524c5 100644 --- a/v2/pkg/parser/generate.go +++ b/v2/pkg/parser/generate.go @@ -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 `/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")