diff --git a/v2/cmd/wails/internal/commands/build/build.go b/v2/cmd/wails/internal/commands/build/build.go index 01e3b911..a5716bbc 100644 --- a/v2/cmd/wails/internal/commands/build/build.go +++ b/v2/cmd/wails/internal/commands/build/build.go @@ -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) }) } diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go index 7c82917a..91e6c3cd 100644 --- a/v2/pkg/commands/build/base.go +++ b/v2/pkg/commands/build/base.go @@ -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 diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 88086f36..0a5ca861 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -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 diff --git a/v2/pkg/commands/build/builder.go b/v2/pkg/commands/build/builder.go index 9bbf79ed..8dda4d57 100644 --- a/v2/pkg/commands/build/builder.go +++ b/v2/pkg/commands/build/builder.go @@ -12,5 +12,6 @@ type Builder interface { BuildFrontend(*clilogger.CLILogger) error BuildRuntime(*Options) error CompileProject(*Options) error + OutputFilename(*Options) string CleanUp() } diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go index f1092291..d3edd182 100644 --- a/v2/pkg/commands/build/desktop.go +++ b/v2/pkg/commands/build/desktop.go @@ -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 diff --git a/v2/pkg/commands/build/server.go b/v2/pkg/commands/build/server.go index 14fe72ad..8e305a8f 100644 --- a/v2/pkg/commands/build/server.go +++ b/v2/pkg/commands/build/server.go @@ -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 }