Refactored build command (#2123)

* Refactored build command

* Update v2/cmd/wails/build.go

Co-authored-by: stffabi <stffabi@users.noreply.github.com>

* WIP

* Refactor `wails doctor`

* Refactor `wails dev`

* Refactor `wails dev`

* Fix merge conflict

* Fix test

* Update build_and_test.yml

Co-authored-by: stffabi <stffabi@users.noreply.github.com>
This commit is contained in:
Lea Anthony
2022-12-01 18:18:02 +11:00
committed by GitHub
co-authored by stffabi
parent 9d53db4281
commit ea6aee91f1
72 changed files with 2311 additions and 2420 deletions
+1
View File
@@ -29,6 +29,7 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Run tests
working-directory: ./v2
run: go test -v ./...
test_templates:
+254
View File
@@ -0,0 +1,254 @@
package main
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"os"
"runtime"
"strings"
"time"
)
func buildApplication(f *flags.Build) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
if quiet {
pterm.DisableOutput()
} else {
app.PrintBanner()
}
err := f.Process()
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: "desktop",
OutputFile: f.OutputFilename,
CleanBinDirectory: f.Clean,
Mode: f.GetBuildMode(),
Pack: !f.NoPackage,
LDFlags: f.LdFlags,
Compiler: f.Compiler,
SkipModTidy: f.SkipModTidy,
Verbosity: f.Verbosity,
ForceBuild: f.ForceBuild,
IgnoreFrontend: f.SkipFrontend,
Compress: f.Upx,
CompressFlags: f.UpxFlags,
UserTags: f.GetTags(),
WebView2Strategy: f.GetWebView2Strategy(),
TrimPath: f.TrimPath,
RaceDetector: f.RaceDetector,
WindowsConsole: f.WindowsConsole,
Obfuscated: f.Obfuscated,
GarbleArgs: f.GarbleArgs,
SkipBindings: f.SkipBindings,
ProjectData: projectOptions,
}
tableData := pterm.TableData{
{"Platform(s)", f.Platform},
{"Compiler", f.GetCompilerPath()},
{"Skip Bindings", bool2Str(f.SkipBindings)},
{"Build Mode", f.GetBuildModeAsString()},
{"Frontend Directory", projectOptions.GetFrontendDir()},
{"Obfuscated", bool2Str(f.Obfuscated)},
}
if f.Obfuscated {
tableData = append(tableData, []string{"Garble Args", f.GarbleArgs})
}
tableData = append(tableData, pterm.TableData{
{"Skip Frontend", bool2Str(f.SkipFrontend)},
{"Compress", bool2Str(f.Upx)},
{"Package", bool2Str(!f.NoPackage)},
{"Clean Bin Dir", bool2Str(f.Clean)},
{"LDFlags", f.LdFlags},
{"Tags", "[" + strings.Join(f.GetTags(), ",") + "]"},
{"Race Detector", bool2Str(f.RaceDetector)},
}...)
if len(buildOptions.OutputFile) > 0 && f.GetTargets().Length() == 1 {
tableData = append(tableData, []string{"Output File", f.OutputFilename})
}
pterm.DefaultSection.Println("Build Options")
err = pterm.DefaultTable.WithData(tableData).Render()
if err != nil {
return err
}
err = gomod.SyncGoMod(logger, f.UpdateWailsVersionGoMod)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
"linux/amd64",
"linux/arm64",
"linux/arm",
"windows",
"windows/amd64",
"windows/arm64",
"windows/386",
})
outputBinaries := map[string]string{}
// Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
// returning an error.
var targetErr error
targets := f.GetTargets()
targets.Each(func(platform string) {
if targetErr != nil {
return
}
if !validPlatformArch.Contains(platform) {
buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
return
}
desiredFilename := projectOptions.OutputFilename
if desiredFilename == "" {
desiredFilename = projectOptions.Name
}
desiredFilename = strings.TrimSuffix(desiredFilename, ".exe")
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = f.GetDefaultArch()
if len(platformSplit) > 1 {
buildOptions.Arch = platformSplit[1]
}
banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch
pterm.DefaultSection.Println(banner)
if f.Upx && platform == "darwin/universal" {
pterm.Warning.Println("Warning: compress flag unsupported for universal binaries. Ignoring.")
f.Upx = false
}
switch buildOptions.Platform {
case "linux":
if runtime.GOOS != "linux" {
pterm.Warning.Println("Crosscompiling to Linux not currently supported.\n")
return
}
case "darwin":
if runtime.GOOS != "darwin" {
pterm.Warning.Println("Crosscompiling to Mac not currently supported.\n")
return
}
macTargets := targets.Filter(func(platform string) bool {
return strings.HasPrefix(platform, "darwin")
})
if macTargets.Length() == 2 {
buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch)
}
}
if targets.Length() > 1 {
// target filename
switch buildOptions.Platform {
case "windows":
desiredFilename = fmt.Sprintf("%s-%s", desiredFilename, buildOptions.Arch)
case "linux", "darwin":
desiredFilename = fmt.Sprintf("%s-%s-%s", desiredFilename, buildOptions.Platform, buildOptions.Arch)
}
}
if buildOptions.Platform == "windows" {
desiredFilename += ".exe"
}
buildOptions.OutputFile = desiredFilename
if f.OutputFilename != "" {
buildOptions.OutputFile = f.OutputFilename
}
if f.Obfuscated && f.SkipBindings {
pterm.Warning.Println("obfuscated flag overrides skipbindings flag.")
buildOptions.SkipBindings = false
}
if !f.DryRun {
// Start Time
start := time.Now()
compiledBinary, err := build.Build(buildOptions)
if err != nil {
pterm.Error.Println(err.Error())
targetErr = err
return
}
buildOptions.IgnoreFrontend = true
buildOptions.CleanBinDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary
} else {
pterm.Info.Println("Dry run: skipped build.")
}
})
if targetErr != nil {
return targetErr
}
if f.DryRun {
return nil
}
if f.NSIS {
amd64Binary := outputBinaries["windows/amd64"]
arm64Binary := outputBinaries["windows/arm64"]
if amd64Binary == "" && arm64Binary == "" {
return fmt.Errorf("cannot build nsis installer - no windows targets")
}
if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
return err
}
}
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package main
import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/dev"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"os"
)
func devApplication(f *flags.Dev) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
if quiet {
pterm.DisableOutput()
} else {
app.PrintBanner()
}
err := f.Process()
if err != nil {
return err
}
return dev.Application(f, logger)
}
+165
View File
@@ -0,0 +1,165 @@
package main
import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"runtime"
"runtime/debug"
"strings"
)
func diagnoseEnvironment(f *flags.Doctor) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
app.PrintBanner()
pterm.Print("Scanning system - Please wait (this may take a long time)...")
// Get system info
info, err := system.GetInfo()
if err != nil {
pterm.Println("Failed.")
return err
}
pterm.Println("Done.")
pterm.DefaultSection.Println("System")
systemTabledata := [][]string{
{"OS", info.OS.Name},
{"Version", info.OS.Version},
{"ID", info.OS.ID},
{"Go Version", runtime.Version()},
{"Platform", runtime.GOOS},
{"Architecture", runtime.GOARCH},
}
err = pterm.DefaultTable.WithData(systemTabledata).Render()
if err != nil {
return err
}
pterm.DefaultSection.Println("Wails")
wailsTableData := [][]string{
{"Version", app.Version()},
}
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
buildSettingToName := map[string]string{
"vcs.revision": "Revision",
"vcs.modified": "Modified",
}
for _, buildSetting := range buildInfo.Settings {
name := buildSettingToName[buildSetting.Key]
if name == "" {
continue
}
wailsTableData = append(wailsTableData, []string{name, buildSetting.Value})
}
}
// Exit early if PM not found
if info.PM != nil {
wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()})
}
err = pterm.DefaultTable.WithData(wailsTableData).Render()
if err != nil {
return err
}
pterm.DefaultSection.Println("Dependencies")
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependency{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
dependenciesTableData := [][]string{
{"Dependency", "Package Name", "Status", "Version"},
}
hasOptionalDependencies := false
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name = "*" + name
hasOptionalDependencies = true
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
dependenciesTableData = append(dependenciesTableData, []string{name, packageName, status, dependency.Version})
}
err = pterm.DefaultTable.WithHasHeader(true).WithData(dependenciesTableData).Render()
if hasOptionalDependencies {
pterm.Println("* - Optional Dependency")
}
pterm.DefaultSection.Println("Diagnosis")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
pterm.Println("Your system is ready for Wails development!")
} else {
pterm.Println("Your system has missing dependencies!\n")
}
if dependenciesAvailableRequired != 0 {
pterm.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
pterm.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
}
if len(dependenciesMissing) != 0 {
pterm.Println("Fatal:")
pterm.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
pterm.Println("Please read this article on how to resolve this: https://wails.io/guides/resolving-missing-packages")
}
return nil
}
+166
View File
@@ -0,0 +1,166 @@
package flags
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"os"
"os/exec"
"runtime"
"strings"
)
const (
Quiet int = 0
Normal int = 1
Verbose int = 2
)
// TODO: unify this and `build.Options`
type Build struct {
Common
BuildCommon
NoPackage bool `name:"noPackage" description:"Skips platform specific packaging"`
SkipModTidy bool `name:"m" description:"Skip mod tidy before compile"`
Upx bool `description:"Compress final binary with UPX (if installed)"`
UpxFlags string `description:"Flags to pass to upx"`
Platform string `description:"Platform to target. Comma separate multiple platforms"`
OutputFilename string `name:"o" description:"Output filename"`
Clean bool `description:"Clean the bin directory before building"`
WebView2 string `description:"WebView2 installer strategy: download,embed,browser,error"`
ForceBuild bool `name:"f" description:"Force build of application"`
UpdateWailsVersionGoMod bool `name:"u" description:"Updates go.mod to use the same Wails version as the CLI"`
Debug bool `description:"Builds the application in debug mode"`
NSIS bool `description:"Generate NSIS installer for Windows"`
TrimPath bool `description:"Remove all file system paths from the resulting executable"`
WindowsConsole bool `description:"Keep the console when building for Windows"`
Obfuscated bool `description:"Code obfuscation of bound Wails methods"`
GarbleArgs string `description:"Arguments to pass to garble"`
DryRun bool `description:"Prints the build command without executing it"`
// Build Specific
// Internal state
compilerPath string
userTags []string
wv2rtstrategy string // WebView2 runtime strategy
defaultArch string // Default architecture
}
func (b *Build) Default() *Build {
defaultPlatform := os.Getenv("GOOS")
if defaultPlatform == "" {
defaultPlatform = runtime.GOOS
}
defaultArch := os.Getenv("GOARCH")
if defaultArch == "" {
if system.IsAppleSilicon {
defaultArch = "arm64"
} else {
defaultArch = runtime.GOARCH
}
}
b.defaultArch = defaultArch
platform := defaultPlatform + "/" + defaultArch
result := &Build{
Platform: platform,
WebView2: "download",
GarbleArgs: "-literals -tiny -seed=random",
}
result.BuildCommon = result.BuildCommon.Default()
return result
}
func (b *Build) GetBuildMode() build.Mode {
if b.Debug {
return build.Debug
}
return build.Production
}
func (b *Build) GetWebView2Strategy() string {
return b.wv2rtstrategy
}
func (b *Build) GetTargets() *slicer.StringSlicer {
var targets slicer.StringSlicer
targets.AddSlice(strings.Split(b.Platform, ","))
targets.Deduplicate()
return &targets
}
func (b *Build) GetCompilerPath() string {
return b.compilerPath
}
func (b *Build) GetTags() []string {
return b.userTags
}
func (b *Build) Process() error {
// Lookup compiler path
var err error
b.compilerPath, err = exec.LookPath(b.Compiler)
if err != nil {
return fmt.Errorf("unable to find compiler: %s", b.Compiler)
}
// Process User Tags
b.userTags, err = buildtags.Parse(b.Tags)
if err != nil {
return err
}
// WebView2 installer strategy (download by default)
b.WebView2 = strings.ToLower(b.WebView2)
if b.WebView2 != "" {
validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
if !validWV2Runtime.Contains(b.WebView2) {
return fmt.Errorf("invalid option for flag 'webview2': %s", b.WebView2)
}
b.wv2rtstrategy = "wv2runtime." + b.WebView2
}
return nil
}
func bool2Str(b bool) string {
if b {
return "true"
}
return "false"
}
func (b *Build) GetBuildModeAsString() string {
if b.Debug {
return "debug"
}
return "production"
}
func (b *Build) GetDefaultArch() string {
return b.defaultArch
}
/*
_, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
}
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
_, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
_, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
_, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
_, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
if len(buildOptions.OutputFile) > 0 && targets.Length() == 1 {
_, _ = fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
*/
+18
View File
@@ -0,0 +1,18 @@
package flags
type BuildCommon struct {
LdFlags string `description:"Additional ldflags to pass to the compiler"`
Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"`
SkipBindings bool `description:"Skips generation of bindings"`
RaceDetector bool `name:"race" description:"Build with Go's race detector"`
SkipFrontend bool `name:"s" description:"Skips building the frontend"`
Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"`
Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"`
}
func (c BuildCommon) Default() BuildCommon {
return BuildCommon{
Compiler: "go",
Verbosity: 1,
}
}
+5
View File
@@ -0,0 +1,5 @@
package flags
type Common struct {
NoColour bool `description:"Disable colour in output"`
}
+144
View File
@@ -0,0 +1,144 @@
package flags
import (
"fmt"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"net"
"net/url"
"os"
"path/filepath"
"runtime"
)
type Dev struct {
BuildCommon
AssetDir string `flag:"assetdir" description:"Serve assets from the given directory instead of using the provided asset FS"`
Extensions string `flag:"e" description:"Extensions to trigger rebuilds (comma separated) eg go"`
ReloadDirs string `flag:"reloaddirs" description:"Additional directories to trigger reloads (comma separated)"`
Browser bool `flag:"browser" description:"Open the application in a browser"`
NoReload bool `flag:"noreload" description:"Disable reload on asset change"`
NoColour bool `flag:"nocolor" description:"Disable colour in output"`
WailsJSDir string `flag:"wailsjsdir" description:"Directory to generate the Wails JS modules"`
LogLevel string `flag:"loglevel" description:"LogLevel to use - Trace, Debug, Info, Warning, Error)"`
ForceBuild bool `flag:"f" description:"Force build of application"`
Debounce int `flag:"debounce" description:"The amount of time to wait to trigger a reload on change"`
DevServer string `flag:"devserver" description:"The address of the wails dev server"`
AppArgs string `flag:"appargs" description:"arguments to pass to the underlying app (quoted and space separated)"`
Save bool `flag:"save" description:"Save the given flags as defaults"`
FrontendDevServerURL string `flag:"frontenddevserverurl" description:"The url of the external frontend dev server to use"`
// Internal state
devServerURL *url.URL
projectConfig *project.Project
}
func (*Dev) Default() *Dev {
result := &Dev{
Extensions: "go",
Debounce: 100,
}
result.BuildCommon = result.BuildCommon.Default()
return result
}
func (d *Dev) Process() error {
var err error
err = d.loadAndMergeProjectConfig()
if err != nil {
return err
}
if _, _, err := net.SplitHostPort(d.DevServer); err != nil {
return fmt.Errorf("DevServer is not of the form 'host:port', please check your wails.json")
}
d.devServerURL, err = url.Parse("http://" + d.DevServer)
if err != nil {
return err
}
return nil
}
func (d *Dev) loadAndMergeProjectConfig() error {
var err error
cwd, err := os.Getwd()
if err != nil {
return err
}
d.projectConfig, err = project.Load(cwd)
if err != nil {
return err
}
d.AssetDir, _ = lo.Coalesce(d.AssetDir, d.projectConfig.AssetDirectory)
d.projectConfig.AssetDirectory = filepath.ToSlash(d.AssetDir)
if d.AssetDir != "" {
d.AssetDir, err = filepath.Abs(d.AssetDir)
if err != nil {
return err
}
}
d.ReloadDirs, _ = lo.Coalesce(d.ReloadDirs, d.projectConfig.ReloadDirectories)
d.projectConfig.ReloadDirectories = filepath.ToSlash(d.ReloadDirs)
d.DevServer, _ = lo.Coalesce(d.DevServer, d.projectConfig.DevServer)
d.projectConfig.DevServer = d.DevServer
d.FrontendDevServerURL, _ = lo.Coalesce(d.FrontendDevServerURL, d.projectConfig.FrontendDevServerURL)
d.projectConfig.FrontendDevServerURL = d.FrontendDevServerURL
d.WailsJSDir, _ = lo.Coalesce(d.WailsJSDir, d.projectConfig.GetWailsJSDir(), d.projectConfig.GetFrontendDir())
d.projectConfig.WailsJSDir = filepath.ToSlash(d.WailsJSDir)
if d.Debounce == 100 && d.projectConfig.DebounceMS != 100 {
if d.projectConfig.DebounceMS == 0 {
d.projectConfig.DebounceMS = 100
}
d.Debounce = d.projectConfig.DebounceMS
}
d.projectConfig.DebounceMS = d.Debounce
d.AppArgs, _ = lo.Coalesce(d.AppArgs, d.projectConfig.AppArgs)
if d.Save {
err = d.projectConfig.Save()
if err != nil {
return err
}
}
return nil
}
// GenerateBuildOptions creates a build.Options using the flags
func (d *Dev) GenerateBuildOptions() *build.Options {
result := &build.Options{
OutputType: "dev",
Mode: build.Dev,
Arch: runtime.GOARCH,
Pack: true,
Platform: runtime.GOOS,
LDFlags: d.LdFlags,
Compiler: d.Compiler,
ForceBuild: d.ForceBuild,
IgnoreFrontend: d.SkipFrontend,
Verbosity: d.Verbosity,
WailsJSDir: d.WailsJSDir,
RaceDetector: d.RaceDetector,
ProjectData: d.projectConfig,
}
return result
}
func (d *Dev) ProjectConfig() *project.Project {
return d.projectConfig
}
func (d *Dev) DevServerURL() *url.URL {
return d.devServerURL
}
+9
View File
@@ -0,0 +1,9 @@
package flags
type Doctor struct {
Common
}
func (b *Doctor) Default() *Doctor {
return &Doctor{}
}
+14
View File
@@ -0,0 +1,14 @@
package flags
type GenerateModule struct {
Common
Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"`
Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"`
}
type GenerateTemplate struct {
Common
Name string `description:"Name of the template to generate"`
Frontend string `description:"Frontend to use for the template"`
Quiet bool `description:"Suppress output"`
}
+22
View File
@@ -0,0 +1,22 @@
package flags
type Init struct {
Common
TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url"`
ProjectName string `name:"n" description:"Name of project"`
CIMode bool `name:"ci" description:"CI Mode"`
ProjectDir string `name:"d" description:"Project directory"`
Quiet bool `name:"q" description:"Suppress output to console"`
InitGit bool `name:"g" description:"Initialise git repository"`
IDE string `name:"ide" description:"Generate IDE project files"`
List bool `name:"l" description:"List templates"`
}
func (i *Init) Default() *Init {
result := &Init{
TemplateName: "vanilla",
}
return result
}
+6
View File
@@ -0,0 +1,6 @@
package flags
type ShowReleaseNotes struct {
Common
Version string `description:"The version to show the release notes for"`
}
+7
View File
@@ -0,0 +1,7 @@
package flags
type Update struct {
Common
Version string `description:"The version to update to"`
PreRelease bool `name:"pre" description:"Update to latest pre-release"`
}
+245
View File
@@ -0,0 +1,245 @@
package main
import (
"fmt"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/pterm/pterm"
"github.com/tidwall/sjson"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/template"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"os"
"path/filepath"
)
func generateModule(f *flags.GenerateModule) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
buildTags, err := buildtags.Parse(f.Tags)
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
_, err = bindings.GenerateBindings(bindings.Options{
Tags: buildTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
return nil
}
func generateTemplate(f *flags.GenerateTemplate) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Quiet
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
// name is mandatory
if f.Name == "" {
return fmt.Errorf("please provide a template name using the -name flag")
}
// If the current directory is not empty, we create a new directory
cwd, err := os.Getwd()
if err != nil {
return err
}
templateDir := filepath.Join(cwd, f.Name)
if !fs.DirExists(templateDir) {
err := os.MkdirAll(templateDir, 0755)
if err != nil {
return err
}
}
empty, err := fs.DirIsEmpty(templateDir)
if err != nil {
return err
}
pterm.DefaultSection.Println("Generating template")
if !empty {
templateDir = filepath.Join(cwd, f.Name)
printBulletPoint("Creating new template directory:", f.Name)
err = fs.Mkdir(templateDir)
if err != nil {
return err
}
}
// Create base template
baseTemplate, err := debme.FS(template.Base, "base")
if err != nil {
return err
}
g := gosod.New(baseTemplate)
g.SetTemplateFilters([]string{".template"})
err = os.Chdir(templateDir)
if err != nil {
return err
}
type templateData struct {
Name string
Description string
TemplateDir string
WailsVersion string
}
printBulletPoint("Extracting base template files...")
err = g.Extract(templateDir, &templateData{
Name: f.Name,
TemplateDir: templateDir,
WailsVersion: app.Version(),
})
if err != nil {
return err
}
err = os.Chdir(cwd)
if err != nil {
return err
}
// If we aren't migrating the files, just exit
if f.Frontend == "" {
pterm.Println()
pterm.Println()
pterm.Info.Println("No frontend specified to migrate. Template created.")
pterm.Println()
return nil
}
// Remove frontend directory
frontendDir := filepath.Join(templateDir, "frontend")
err = os.RemoveAll(frontendDir)
if err != nil {
return err
}
// Copy the files into a new frontend directory
printBulletPoint("Migrating existing project files to frontend directory...")
sourceDir, err := filepath.Abs(f.Frontend)
if err != nil {
return err
}
newFrontendDir := filepath.Join(templateDir, "frontend")
err = fs.CopyDirExtended(sourceDir, newFrontendDir, []string{f.Name, "node_modules"})
if err != nil {
return err
}
// Process package.json
err = processPackageJSON(frontendDir)
if err != nil {
return err
}
// Process package-lock.json
err = processPackageLockJSON(frontendDir)
if err != nil {
return err
}
// Remove node_modules - ignore error, eg it doesn't exist
_ = os.RemoveAll(filepath.Join(frontendDir, "node_modules"))
return nil
}
func processPackageJSON(frontendDir string) error {
var err error
packageJSON := filepath.Join(frontendDir, "package.json")
if !fs.FileExists(packageJSON) {
return fmt.Errorf("no package.json found - cannot process")
}
json, err := os.ReadFile(packageJSON)
if err != nil {
return err
}
// We will ignore these errors - it's not critical
printBulletPoint("Updating package.json data...")
json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}")
json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}")
err = os.WriteFile(packageJSON, json, 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(packageJSON)
printBulletPoint("Renaming package.json -> package.tmpl.json...")
err = os.Rename(packageJSON, filepath.Join(baseDir, "package.tmpl.json"))
if err != nil {
return err
}
return nil
}
func processPackageLockJSON(frontendDir string) error {
var err error
filename := filepath.Join(frontendDir, "package-lock.json")
if !fs.FileExists(filename) {
return fmt.Errorf("no package-lock.json found - cannot process")
}
data, err := os.ReadFile(filename)
if err != nil {
return err
}
json := string(data)
// We will ignore these errors - it's not critical
printBulletPoint("Updating package-lock.json data...")
json, _ = sjson.Set(json, "name", "{{.ProjectName}}")
err = os.WriteFile(filename, []byte(json), 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(filename)
printBulletPoint("Renaming package-lock.json -> package-lock.tmpl.json...")
err = os.Rename(filename, filepath.Join(baseDir, "package-lock.tmpl.json"))
if err != nil {
return err
}
return nil
}
+278
View File
@@ -0,0 +1,278 @@
package main
import (
"bufio"
"fmt"
"github.com/flytam/filenamify"
"github.com/leaanthony/slicer"
"github.com/pkg/errors"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
"github.com/wailsapp/wails/v2/pkg/templates"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func initProject(f *flags.Init) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
// Are we listing templates?
if f.List {
app.PrintBanner()
templateList, err := templates.List()
if err != nil {
return err
}
pterm.DefaultSection.Println("Available templates")
table := pterm.TableData{{"Template", "Short Name", "Description"}}
for _, template := range templateList {
table = append(table, []string{template.Name, template.ShortName, template.Description})
}
err = pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render()
pterm.Println()
return err
}
// Validate name
if len(f.ProjectName) == 0 {
return fmt.Errorf("please provide a project name using the -n flag")
}
// Validate IDE option
supportedIDEs := slicer.String([]string{"vscode", "goland"})
ide := strings.ToLower(f.IDE)
if ide != "" {
if !supportedIDEs.Contains(ide) {
return fmt.Errorf("ide '%s' not supported. Valid values: %s", ide, supportedIDEs.Join(" "))
}
}
if !quiet {
app.PrintBanner()
}
pterm.DefaultSection.Printf("Initialising Project '%s'", f.ProjectName)
projectFilename, err := filenamify.Filenamify(f.ProjectName, filenamify.Options{
Replacement: "_",
MaxLength: 255,
})
if err != nil {
return err
}
goBinary, err := exec.LookPath("go")
if err != nil {
return fmt.Errorf("unable to find Go compiler. Please download and install Go: https://golang.org/dl/")
}
// Get base path and convert to forward slashes
goPath := filepath.ToSlash(filepath.Dir(goBinary))
// Trim bin directory
goSDKPath := strings.TrimSuffix(goPath, "/bin")
// Create Template Options
options := &templates.Options{
ProjectName: f.ProjectName,
TargetDir: f.ProjectDir,
TemplateName: f.TemplateName,
Logger: logger,
IDE: ide,
InitGit: f.InitGit,
ProjectNameFilename: projectFilename,
WailsVersion: app.Version(),
GoSDKPath: goSDKPath,
}
// Try to discover author details from git config
findAuthorDetails(options)
// Start Time
start := time.Now()
// Install the template
remote, template, err := templates.Install(options)
if err != nil {
return err
}
// Install the default assets
err = buildassets.Install(options.TargetDir)
if err != nil {
return err
}
err = os.Chdir(options.TargetDir)
if err != nil {
return err
}
if !f.CIMode {
// Run `go mod tidy` to ensure `go.sum` is up to date
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = options.TargetDir
cmd.Stderr = os.Stderr
if !quiet {
cmd.Stdout = os.Stdout
}
err = cmd.Run()
if err != nil {
return err
}
} else {
// Update go mod
workspace := os.Getenv("GITHUB_WORKSPACE")
pterm.Println("GitHub workspace:", workspace)
if workspace == "" {
os.Exit(1)
}
updateReplaceLine(workspace)
}
// Remove the `.git`` directory in the template project
err = os.RemoveAll(".git")
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
return err
}
}
if quiet {
return nil
}
// Output stats
elapsed := time.Since(start)
// Create pterm table
table := pterm.TableData{
{"Project Name", options.ProjectName},
{"Project Directory", options.TargetDir},
{"Template", template.Name},
{"Template Source", template.HelpURL},
}
err = pterm.DefaultTable.WithData(table).Render()
if err != nil {
return err
}
// IDE message
switch options.IDE {
case "vscode":
pterm.Println()
pterm.Info.Println("VSCode config files generated.")
case "goland":
pterm.Println()
pterm.Info.Println("Goland config files generated.")
}
if options.InitGit {
pterm.Info.Println("Git repository initialised.")
}
if remote {
pterm.Warning.Println("NOTE: You have created a project using a remote template. The Wails project takes no responsibility for 3rd party templates. Only use remote templates that you trust.")
}
pterm.Println("")
pterm.Printf("Initialised project '%s' in %s.\n", options.ProjectName, elapsed.Round(time.Millisecond).String())
pterm.Println("")
return nil
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
ignore := []string{
"build/bin",
"frontend/dist",
"frontend/node_modules",
}
err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0644)
if err != nil {
return errors.Wrap(err, "Unable to create gitignore")
}
return nil
}
// findAuthorDetails tries to find the user's name and email
// from gitconfig. If it finds them, it stores them in the project options
func findAuthorDetails(options *templates.Options) {
if git.IsInstalled() {
name, err := git.Name()
if err == nil {
options.AuthorName = strings.TrimSpace(name)
}
email, err := git.Email()
if err == nil {
options.AuthorEmail = strings.TrimSpace(email)
}
}
}
func updateReplaceLine(targetPath string) {
file, err := os.Open("go.mod")
if err != nil {
fatal(err.Error())
}
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
err = file.Close()
if err != nil {
fatal(err.Error())
}
if err := scanner.Err(); err != nil {
fatal(err.Error())
}
for i, line := range lines {
println(line)
if strings.HasPrefix(line, "// replace") {
pterm.Println("Found replace line")
splitLine := strings.Split(line, " ")
splitLine[5] = targetPath + "/v2"
lines[i] = strings.Join(splitLine[1:], " ")
continue
}
}
err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
fatal(err.Error())
}
}
@@ -1,46 +0,0 @@
# 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 | |
| -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 |
| -delve | If true, runs delve on the compiled binary | false |
## 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.
- The platform assets in the `build/<platform>` directory are processed: manifest + icons compiled to a `.syso` file (
deleted after compilation), `info.plist` copied to `.app` on Mac.
- 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.
@@ -1,401 +0,0 @@
package build
import (
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
command := app.NewSubCommand("build", "Builds the application")
// Setup noPackage flag
noPackage := false
command.BoolFlag("noPackage", "Skips platform specific packaging", &noPackage)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
skipModTidy := false
command.BoolFlag("m", "Skip mod tidy before compile", &skipModTidy)
compress := false
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
compressFlags := ""
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
defaultPlatform := os.Getenv("GOOS")
if defaultPlatform == "" {
defaultPlatform = runtime.GOOS
}
defaultArch := os.Getenv("GOARCH")
if defaultArch == "" {
if system.IsAppleSilicon {
defaultArch = "arm64"
} else {
defaultArch = runtime.GOARCH
}
}
platform := defaultPlatform + "/" + defaultArch
command.StringFlag("platform", "Platform to target. Comma separate multiple platforms", &platform)
// 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)
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated", &tags)
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
// Clean bin directory
cleanBinDirectory := false
command.BoolFlag("clean", "Clean the bin directory before building", &cleanBinDirectory)
webview2 := "download"
command.StringFlag("webview2", "WebView2 installer strategy: download,embed,browser,error.", &webview2)
skipFrontend := false
command.BoolFlag("s", "Skips building the frontend", &skipFrontend)
forceBuild := false
command.BoolFlag("f", "Force build application", &forceBuild)
updateGoModWailsVersion := false
command.BoolFlag("u", "Updates go.mod to use the same Wails version as the CLI", &updateGoModWailsVersion)
debug := false
command.BoolFlag("debug", "Retains debug data in the compiled application", &debug)
nsis := false
command.BoolFlag("nsis", "Generate NSIS installer for Windows", &nsis)
trimpath := false
command.BoolFlag("trimpath", "Remove all file system paths from the resulting executable", &trimpath)
raceDetector := false
command.BoolFlag("race", "Build with Go's race detector", &raceDetector)
windowsConsole := false
command.BoolFlag("windowsconsole", "Keep the console when building for Windows", &windowsConsole)
obfuscated := false
command.BoolFlag("obfuscated", "Code obfuscation of bound Wails methods", &obfuscated)
garbleargs := "-literals -tiny -seed=random"
command.StringFlag("garbleargs", "Arguments to pass to garble", &garbleargs)
dryRun := false
command.BoolFlag("dryrun", "Dry run, prints the config for the command that would be executed", &dryRun)
skipBindings := false
command.BoolFlag("skipbindings", "Skips generation of bindings", &skipBindings)
command.Action(func() error {
quiet := verbosity == 0
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
if !quiet {
app.PrintBanner()
}
// Lookup compiler path
compilerPath, err := exec.LookPath(compilerCommand)
if err != nil {
return fmt.Errorf("unable to find compiler: %s", compilerCommand)
}
// Process User Tags
userTags, err := buildtags.Parse(tags)
if err != nil {
return err
}
// Webview2 installer strategy (download by default)
wv2rtstrategy := ""
webview2 = strings.ToLower(webview2)
if webview2 != "" {
validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
if !validWV2Runtime.Contains(webview2) {
return fmt.Errorf("invalid option for flag 'webview2': %s", webview2)
}
// These are the build tags associated with the strategies
switch webview2 {
case "embed":
wv2rtstrategy = "wv2runtime.embed"
case "error":
wv2rtstrategy = "wv2runtime.error"
case "browser":
wv2rtstrategy = "wv2runtime.browser"
}
}
mode := build.Production
modeString := "Production"
if debug {
mode = build.Debug
modeString = "Debug"
}
var targets slicer.StringSlicer
targets.AddSlice(strings.Split(platform, ","))
targets.Deduplicate()
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
CleanBinDirectory: cleanBinDirectory,
Mode: mode,
Pack: !noPackage,
LDFlags: ldflags,
Compiler: compilerCommand,
SkipModTidy: skipModTidy,
Verbosity: verbosity,
ForceBuild: forceBuild,
IgnoreFrontend: skipFrontend,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
WebView2Strategy: wv2rtstrategy,
TrimPath: trimpath,
RaceDetector: raceDetector,
WindowsConsole: windowsConsole,
Obfuscated: obfuscated,
GarbleArgs: garbleargs,
SkipBindings: skipBindings,
ProjectData: projectOptions,
}
// Start a new tabwriter
if !quiet {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
_, _ = fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
_, _ = fmt.Fprintf(w, "Platforms: \t%s\n", platform)
_, _ = fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
_, _ = fmt.Fprintf(w, "Skip Bindings: \t%t\n", skipBindings)
_, _ = fmt.Fprintf(w, "Build Mode: \t%s\n", modeString)
_, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
}
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
_, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
_, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
_, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
_, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
if len(buildOptions.OutputFile) > 0 && targets.Length() == 1 {
_, _ = fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
_, _ = fmt.Fprintf(w, "\n")
err = w.Flush()
if err != nil {
return err
}
}
err = SyncGoMod(logger, updateGoModWailsVersion)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
"linux/amd64",
"linux/arm64",
"linux/arm",
"windows",
"windows/amd64",
"windows/arm64",
"windows/386",
})
outputBinaries := map[string]string{}
// Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
// returning an error.
var targetErr error
targets.Each(func(platform string) {
if targetErr != nil {
return
}
if !validPlatformArch.Contains(platform) {
buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
return
}
desiredFilename := projectOptions.OutputFilename
if desiredFilename == "" {
desiredFilename = projectOptions.Name
}
desiredFilename = strings.TrimSuffix(desiredFilename, ".exe")
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = defaultArch
if len(platformSplit) > 1 {
buildOptions.Arch = platformSplit[1]
}
banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch
logger.Println(banner)
logger.Println(strings.Repeat("-", len(banner)))
if compress && platform == "darwin/universal" {
logger.Println("Warning: compress flag unsupported for universal binaries. Ignoring.")
compress = false
}
switch buildOptions.Platform {
case "linux":
if runtime.GOOS != "linux" {
logger.Println("Crosscompiling to Linux not currently supported.\n")
return
}
case "darwin":
if runtime.GOOS != "darwin" {
logger.Println("Crosscompiling to Mac not currently supported.\n")
return
}
macTargets := targets.Filter(func(platform string) bool {
return strings.HasPrefix(platform, "darwin")
})
if macTargets.Length() == 2 {
buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch)
}
}
if targets.Length() > 1 {
// target filename
switch buildOptions.Platform {
case "windows":
desiredFilename = fmt.Sprintf("%s-%s", desiredFilename, buildOptions.Arch)
case "linux", "darwin":
desiredFilename = fmt.Sprintf("%s-%s-%s", desiredFilename, buildOptions.Platform, buildOptions.Arch)
}
}
if buildOptions.Platform == "windows" {
desiredFilename += ".exe"
}
buildOptions.OutputFile = desiredFilename
if outputFilename != "" {
buildOptions.OutputFile = outputFilename
}
if obfuscated && skipBindings {
logger.Println("Warning: obfuscated flag overrides skipbindings flag.")
buildOptions.SkipBindings = false
}
if !dryRun {
// Start Time
start := time.Now()
compiledBinary, err := build.Build(buildOptions)
if err != nil {
logger.Println("Error: %s", err.Error())
targetErr = err
return
}
buildOptions.IgnoreFrontend = true
buildOptions.CleanBinDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary
} else {
logger.Println("Dry run: skipped build.")
}
})
if targetErr != nil {
return targetErr
}
if dryRun {
return nil
}
if nsis {
amd64Binary := outputBinaries["windows/amd64"]
arm64Binary := outputBinaries["windows/arm64"]
if amd64Binary == "" && arm64Binary == "" {
return fmt.Errorf("cannot build nsis installer - no windows targets")
}
if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
return err
}
}
return nil
})
}
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
@@ -1,22 +0,0 @@
# 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 built using a special mode that starts a webserver and starts listening to port 34115. When the frontend project is run independently, so long as the JS is wrapped with the runtime method `ready`, then the frontend will connect to the backend code via websockets. The interface should be present in your browser, and you should be able to interact with the backend as you would in a desktop app.
File diff suppressed because it is too large Load Diff
@@ -1,174 +0,0 @@
package doctor
import (
"fmt"
"io"
"os"
"runtime"
"runtime/debug"
"strings"
"text/tabwriter"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `doctor` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("doctor", "Diagnose your environment")
command.Action(func() error {
logger := clilogger.New(w)
app.PrintBanner()
logger.Print("Scanning system - Please wait (this may take a long time)...")
// Get system info
info, err := system.GetInfo()
if err != nil {
logger.Println("Failed.")
return err
}
logger.Println("Done.")
logger.Println("")
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
fmt.Fprintf(w, "System\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
// Output Go Information
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
// Write out the wails information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Wails\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "Version: ", app.Version())
printBuildSettings(w)
// Exit early if PM not found
if info.PM != nil {
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
}
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependency{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
hasOptionalDependencies := false
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name = "*" + name
hasOptionalDependencies = true
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
fmt.Fprintf(w, "%s \t%s \t%s \t%s\n", name, packageName, status, dependency.Version)
}
if hasOptionalDependencies {
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "* - Optional Dependency\n")
}
w.Flush()
logger.Println("")
logger.Println("Diagnosis")
logger.Println("---------")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
logger.Println("Your system is ready for Wails development!")
} else {
logger.Println("Your system has missing dependencies!\n")
}
if dependenciesAvailableRequired != 0 {
logger.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
logger.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
}
if len(dependenciesMissing) != 0 {
logger.Println("Fatal:")
logger.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
logger.Println("Please read this article on how to resolve this: https://wails.io/guides/resolving-missing-packages")
}
logger.Println("")
return nil
})
return nil
}
func printBuildSettings(w *tabwriter.Writer) {
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
buildSettingToName := map[string]string{
"vcs.revision": "Revision",
"vcs.modified": "Modified",
}
for _, buildSetting := range buildInfo.Settings {
name := buildSettingToName[buildSetting.Key]
if name == "" {
continue
}
_, _ = fmt.Fprintf(w, "%s:\t%s\n", name, buildSetting.Value)
}
}
}

Some files were not shown because too many files have changed in this diff Show More