Files

543 lines
14 KiB
Go
Raw Permalink Normal View History

2019-02-04 18:49:56 +11:00
package cmd
import (
"fmt"
2019-02-04 21:09:56 +11:00
"os"
2019-02-05 08:06:18 +11:00
"os/exec"
"os/user"
2019-02-04 21:09:56 +11:00
"path/filepath"
"runtime"
"strconv"
2020-04-21 06:00:36 +10:00
"strings"
2019-02-05 08:06:18 +11:00
"time"
2019-02-04 18:49:56 +11:00
"github.com/leaanthony/slicer"
"github.com/leaanthony/spinner"
2021-04-02 20:44:55 +11:00
wailsruntime "github.com/wailsapp/wails/runtime"
2019-02-04 18:49:56 +11:00
)
2021-04-17 08:54:37 +10:00
const xgoVersion = "1.16.3"
2020-10-29 20:18:52 +11:00
2019-03-02 12:54:10 +11:00
var fs = NewFSHelper()
2019-02-04 18:49:56 +11:00
// ValidateFrontendConfig checks if the frontend config is valid
func ValidateFrontendConfig(projectOptions *ProjectOptions) error {
if projectOptions.FrontEnd.Dir == "" {
return fmt.Errorf("Frontend directory not set in project.json")
}
if projectOptions.FrontEnd.Build == "" {
return fmt.Errorf("Frontend build command not set in project.json")
}
if projectOptions.FrontEnd.Install == "" {
return fmt.Errorf("Frontend install command not set in project.json")
}
if projectOptions.FrontEnd.Bridge == "" {
return fmt.Errorf("Frontend bridge config not set in project.json")
}
return nil
}
2019-02-05 08:11:02 +11:00
// InstallGoDependencies will run go get in the current directory
2020-02-23 06:03:00 +11:00
func InstallGoDependencies(verbose bool) error {
var depSpinner *spinner.Spinner
if !verbose {
depSpinner = spinner.New("Ensuring Dependencies are up to date...")
depSpinner.SetSpinSpeed(50)
depSpinner.Start()
}
2021-04-17 13:44:24 +10:00
err := NewProgramHelper(verbose).RunCommand("go mod tidy")
2019-02-04 18:49:56 +11:00
if err != nil {
2020-02-23 06:03:00 +11:00
if !verbose {
depSpinner.Error()
}
2019-02-04 18:49:56 +11:00
return err
}
2020-02-23 06:03:00 +11:00
if !verbose {
depSpinner.Success()
}
2019-02-04 18:49:56 +11:00
return nil
}
func InitializeCrossCompilation(verbose bool) error {
2020-04-04 13:08:42 -05:00
// Check Docker
if err := CheckIfInstalled("docker"); err != nil {
return err
}
2020-02-20 16:46:52 +01:00
2020-04-04 14:45:03 -05:00
var packSpinner *spinner.Spinner
2020-10-29 20:18:52 +11:00
msg := fmt.Sprintf("Pulling wailsapp/xgo:%s docker image... (may take a while)", xgoVersion)
if !verbose {
2020-10-29 20:18:52 +11:00
packSpinner = spinner.New(msg)
2020-04-04 14:45:03 -05:00
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
} else {
2020-10-29 20:18:52 +11:00
println(msg)
2020-04-04 14:45:03 -05:00
}
err := NewProgramHelper(verbose).RunCommandArray([]string{"docker",
2020-10-29 20:18:52 +11:00
"pull", fmt.Sprintf("wailsapp/xgo:%s", xgoVersion)})
2020-04-04 14:45:03 -05:00
if err != nil {
if packSpinner != nil {
packSpinner.Error()
}
return err
}
if packSpinner != nil {
packSpinner.Success()
}
return nil
}
2020-10-29 20:18:52 +11:00
// BuildDocker builds the project using the cross compiling wailsapp/xgo:<xgoVersion> container
func BuildDocker(binaryName string, buildMode string, projectOptions *ProjectOptions) error {
var packSpinner *spinner.Spinner
if buildMode == BuildModeBridge {
return fmt.Errorf("you cant serve the application in cross-compilation")
}
// Check build directory
buildDirectory := filepath.Join(fs.Cwd(), "build")
if !fs.DirExists(buildDirectory) {
2021-04-02 20:44:55 +11:00
err := fs.MkDir(buildDirectory)
if err != nil {
return err
}
}
2020-04-04 13:08:42 -05:00
buildCommand := slicer.String()
userid := 1000
2021-05-14 14:15:06 +10:00
currentUser, _ := user.Current()
if i, err := strconv.Atoi(currentUser.Uid); err == nil {
2020-04-04 13:08:42 -05:00
userid = i
}
for _, arg := range []string{
"docker",
"run",
"--rm",
"-v", fmt.Sprintf("%s:/build", filepath.Join(fs.Cwd(), "build")),
2020-04-04 13:08:42 -05:00
"-v", fmt.Sprintf("%s:/source", fs.Cwd()),
"-e", fmt.Sprintf("LOCAL_USER_ID=%v", userid),
2020-10-29 20:18:52 +11:00
"-e", fmt.Sprintf("FLAG_TAGS=%s", projectOptions.Tags),
2020-04-04 13:08:42 -05:00
"-e", fmt.Sprintf("FLAG_LDFLAGS=%s", ldFlags(projectOptions, buildMode)),
"-e", "FLAG_V=false",
"-e", "FLAG_X=false",
"-e", "FLAG_RACE=false",
"-e", "FLAG_BUILDMODE=default",
"-e", "FLAG_TRIMPATH=false",
2020-10-29 20:18:52 +11:00
"-e", fmt.Sprintf("TARGETS=%s/%s", projectOptions.Platform, projectOptions.Architecture),
2020-04-04 13:08:42 -05:00
"-e", "GOPROXY=",
"-e", "GO111MODULE=on",
} {
buildCommand.Add(arg)
}
2020-10-29 20:18:52 +11:00
if projectOptions.GoPath != "" {
buildCommand.Add("-v")
buildCommand.Add(fmt.Sprintf("%s:/go", projectOptions.GoPath))
}
buildCommand.Add(fmt.Sprintf("wailsapp/xgo:%s", xgoVersion))
buildCommand.Add(".")
2020-04-04 14:45:03 -05:00
compileMessage := fmt.Sprintf(
2020-10-29 20:18:52 +11:00
"Packing + Compiling project for %s/%s using docker image wailsapp/xgo:%s",
projectOptions.Platform, projectOptions.Architecture, xgoVersion)
2020-04-04 13:08:42 -05:00
if buildMode == BuildModeDebug {
compileMessage += " (Debug Mode)"
}
if !projectOptions.Verbose {
packSpinner = spinner.New(compileMessage + "...")
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
} else {
println(compileMessage)
}
err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
2020-04-04 13:08:42 -05:00
if err != nil {
if packSpinner != nil {
packSpinner.Error()
2020-02-20 16:46:18 +01:00
}
2020-04-04 13:08:42 -05:00
return err
}
if packSpinner != nil {
packSpinner.Success()
}
return nil
}
// BuildNative builds on the target platform itself.
func BuildNative(binaryName string, forceRebuild bool, buildMode string, projectOptions *ProjectOptions) error {
if err := CheckWindres(); err != nil {
return err
}
2019-02-04 18:49:56 +11:00
compileMessage := "Packing + Compiling project"
2019-02-16 06:58:30 +11:00
if buildMode == BuildModeDebug {
2019-02-04 18:49:56 +11:00
compileMessage += " (Debug Mode)"
}
2020-02-23 06:03:00 +11:00
var packSpinner *spinner.Spinner
if !projectOptions.Verbose {
packSpinner = spinner.New(compileMessage + "...")
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
} else {
println(compileMessage)
}
2019-02-04 18:49:56 +11:00
buildCommand := slicer.String()
2020-04-04 13:08:42 -05:00
buildCommand.Add("go")
2020-04-06 09:44:33 -05:00
buildCommand.Add("build")
2019-05-09 19:31:07 +10:00
2020-04-04 13:08:42 -05:00
if binaryName != "" {
2020-04-21 06:00:36 +10:00
// Alter binary name based on OS
switch projectOptions.Platform {
case "windows":
if !strings.HasSuffix(binaryName, ".exe") {
binaryName += ".exe"
}
default:
if strings.HasSuffix(binaryName, ".exe") {
binaryName = strings.TrimSuffix(binaryName, ".exe")
}
}
2020-04-04 13:08:42 -05:00
buildCommand.Add("-o", filepath.Join("build", binaryName))
2019-02-04 18:49:56 +11:00
}
// If we are forcing a rebuild
2020-04-04 13:08:42 -05:00
if forceRebuild {
2019-02-04 18:49:56 +11:00
buildCommand.Add("-a")
}
2020-04-04 13:08:42 -05:00
buildCommand.AddSlice([]string{"-ldflags", ldFlags(projectOptions, buildMode)})
2019-02-20 22:24:47 +11:00
2020-10-29 20:18:52 +11:00
if projectOptions.Tags != "" {
buildCommand.AddSlice([]string{"--tags", projectOptions.Tags})
}
if projectOptions.Verbose {
fmt.Printf("Command: %v\n", buildCommand.AsSlice())
2020-02-19 13:44:30 +01:00
}
2020-04-04 13:28:25 -05:00
err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
2019-02-04 18:49:56 +11:00
if err != nil {
2020-02-23 06:03:00 +11:00
if packSpinner != nil {
packSpinner.Error()
}
2019-02-04 18:49:56 +11:00
return err
}
2020-02-23 06:03:00 +11:00
if packSpinner != nil {
packSpinner.Success()
}
2019-02-20 23:01:33 +11:00
2020-04-04 13:08:42 -05:00
return nil
}
// BuildApplication will attempt to build the project based on the given inputs
func BuildApplication(binaryName string, forceRebuild bool, buildMode string, packageApp bool, projectOptions *ProjectOptions) error {
var err error
2020-04-04 13:28:25 -05:00
if projectOptions.CrossCompile {
if err := InitializeCrossCompilation(projectOptions.Verbose); err != nil {
return err
}
}
helper := NewPackageHelper(projectOptions.Platform)
// Generate windows resources
if projectOptions.Platform == "windows" {
if err := helper.PackageWindows(projectOptions, false); err != nil {
return err
}
}
2020-04-04 13:08:42 -05:00
if projectOptions.CrossCompile {
err = BuildDocker(binaryName, buildMode, projectOptions)
} else {
err = BuildNative(binaryName, forceRebuild, buildMode, projectOptions)
}
if err != nil {
return err
}
2019-03-20 08:11:46 +11:00
if packageApp {
err = PackageApplication(projectOptions)
if err != nil {
return err
}
}
2019-02-04 18:49:56 +11:00
return nil
}
2019-05-09 19:31:07 +10:00
// PackageApplication will attempt to package the application in a platform dependent way
2019-02-04 18:49:56 +11:00
func PackageApplication(projectOptions *ProjectOptions) error {
2020-02-23 06:03:00 +11:00
var packageSpinner *spinner.Spinner
if projectOptions.Verbose {
2020-04-04 13:08:42 -05:00
packageSpinner = spinner.New("Packaging application...")
2020-02-23 06:03:00 +11:00
packageSpinner.SetSpinSpeed(50)
packageSpinner.Start()
}
2020-04-04 13:08:42 -05:00
2020-02-19 13:44:30 +01:00
err := NewPackageHelper(projectOptions.Platform).Package(projectOptions)
2019-02-04 18:49:56 +11:00
if err != nil {
2020-02-23 06:03:00 +11:00
if packageSpinner != nil {
packageSpinner.Error()
}
2019-02-04 18:49:56 +11:00
return err
}
2020-02-23 06:03:00 +11:00
if packageSpinner != nil {
packageSpinner.Success()
}
2019-02-04 18:49:56 +11:00
return nil
}
2019-02-05 08:11:02 +11:00
// BuildFrontend runs the given build command
2020-02-23 06:03:00 +11:00
func BuildFrontend(projectOptions *ProjectOptions) error {
var buildFESpinner *spinner.Spinner
if !projectOptions.Verbose {
buildFESpinner = spinner.New("Building frontend...")
buildFESpinner.SetSpinSpeed(50)
buildFESpinner.Start()
} else {
println("Building frontend...")
}
err := NewProgramHelper(projectOptions.Verbose).RunCommand(projectOptions.FrontEnd.Build)
2019-02-04 18:49:56 +11:00
if err != nil {
2020-02-23 06:03:00 +11:00
if buildFESpinner != nil {
buildFESpinner.Error()
}
2019-02-04 18:49:56 +11:00
return err
}
2020-02-23 06:03:00 +11:00
if buildFESpinner != nil {
buildFESpinner.Success()
}
2019-02-04 18:49:56 +11:00
return nil
}
2019-02-20 22:24:47 +11:00
// CheckWindres checks if Windres is installed and if not, aborts
func CheckWindres() (err error) {
if runtime.GOOS != "windows" { // FIXME: Handle windows cross-compile for windows!
2019-02-20 22:24:47 +11:00
return nil
}
programHelper := NewProgramHelper()
if !programHelper.IsInstalled("windres") {
2019-03-03 11:34:00 +11:00
return fmt.Errorf("windres not installed. It comes by default with mingw. Ensure you have installed mingw correctly")
2019-02-20 22:24:47 +11:00
}
return nil
2020-02-20 16:46:52 +01:00
}
// CheckIfInstalled returns if application is installed
func CheckIfInstalled(application string) (err error) {
programHelper := NewProgramHelper()
if !programHelper.IsInstalled(application) {
return fmt.Errorf("%s not installed. Ensure you have installed %s correctly", application, application)
}
return nil
2019-02-20 22:24:47 +11:00
}
2019-02-05 08:11:02 +11:00
// InstallFrontendDeps attempts to install the frontend dependencies based on the given options
2019-02-13 08:44:53 +11:00
func InstallFrontendDeps(projectDir string, projectOptions *ProjectOptions, forceRebuild bool, caller string) error {
2019-02-04 21:09:56 +11:00
// Install frontend deps
err := os.Chdir(projectOptions.FrontEnd.Dir)
if err != nil {
return err
}
// Check if frontend deps have been updated
2020-02-23 06:03:00 +11:00
var feSpinner *spinner.Spinner
if !projectOptions.Verbose {
feSpinner = spinner.New("Ensuring frontend dependencies are up to date (This may take a while)")
feSpinner.SetSpinSpeed(50)
feSpinner.Start()
} else {
println("Ensuring frontend dependencies are up to date (This may take a while)")
}
2019-02-04 21:09:56 +11:00
requiresNPMInstall := true
// Read in package.json MD5
fs := NewFSHelper()
packageJSONMD5, err := fs.FileMD5("package.json")
if err != nil {
return err
}
const md5sumFile = "package.json.md5"
2019-11-05 14:47:05 +11:00
// If node_modules does not exist, force a rebuild.
nodeModulesPath, err := filepath.Abs(filepath.Join(".", "node_modules"))
if err != nil {
return err
}
if !fs.DirExists(nodeModulesPath) {
forceRebuild = true
}
2019-02-04 21:09:56 +11:00
// If we aren't forcing the install and the md5sum file exists
if !forceRebuild && fs.FileExists(md5sumFile) {
// Yes - read contents
savedMD5sum, err := fs.LoadAsString(md5sumFile)
// File exists
if err == nil {
// Compare md5
if savedMD5sum == packageJSONMD5 {
// Same - no need for reinstall
requiresNPMInstall = false
2020-02-23 06:03:00 +11:00
if feSpinner != nil {
feSpinner.Success("Skipped frontend dependencies (-f to force rebuild)")
} else {
println("Skipped frontend dependencies (-f to force rebuild)")
}
2019-02-04 21:09:56 +11:00
}
}
}
// Md5 sum package.json
// Different? Build
if requiresNPMInstall || forceRebuild {
// Install dependencies
2020-02-23 06:03:00 +11:00
err = NewProgramHelper(projectOptions.Verbose).RunCommand(projectOptions.FrontEnd.Install)
2019-02-04 21:09:56 +11:00
if err != nil {
2020-02-23 06:03:00 +11:00
if feSpinner != nil {
feSpinner.Error()
}
2019-02-04 21:09:56 +11:00
return err
}
2020-02-23 06:03:00 +11:00
if feSpinner != nil {
feSpinner.Success()
}
2019-02-04 21:09:56 +11:00
// Update md5sum file
2021-04-02 20:44:55 +11:00
err := os.WriteFile(md5sumFile, []byte(packageJSONMD5), 0644)
if err != nil {
return err
}
2019-02-04 21:09:56 +11:00
}
2019-10-08 06:12:08 +11:00
// Install the runtime
2021-04-02 20:44:55 +11:00
if caller == "build" {
err = InstallProdRuntime(projectDir, projectOptions)
} else {
err = InstallBridge(projectDir, projectOptions)
}
2019-04-27 09:29:19 +10:00
if err != nil {
return err
}
// Build frontend
2020-02-23 06:03:00 +11:00
err = BuildFrontend(projectOptions)
2019-04-27 09:29:19 +10:00
if err != nil {
return err
}
return nil
}
2019-10-08 06:12:08 +11:00
// InstallBridge installs the relevant bridge javascript library
func InstallBridge(projectDir string, projectOptions *ProjectOptions) error {
bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, "node_modules", "@wailsapp", "runtime", "init.js")
2021-04-02 20:44:55 +11:00
err := fs.CreateFile(bridgeFileTarget, wailsruntime.BridgeJS)
2019-10-08 06:12:08 +11:00
return err
}
// InstallProdRuntime installs the production runtime
func InstallProdRuntime(projectDir string, projectOptions *ProjectOptions) error {
bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, "node_modules", "@wailsapp", "runtime", "init.js")
2021-04-02 20:44:55 +11:00
err := fs.CreateFile(bridgeFileTarget, wailsruntime.InitJS)
2019-10-08 06:12:08 +11:00
return err
2019-02-04 21:09:56 +11:00
}
2019-02-05 08:11:02 +11:00
// ServeProject attempts to serve up the current project so that it may be connected to
// via the Wails bridge
2019-02-05 08:06:18 +11:00
func ServeProject(projectOptions *ProjectOptions, logger *Logger) error {
go func() {
time.Sleep(2 * time.Second)
2020-10-29 20:18:52 +11:00
if projectOptions.Platform == "windows" {
logger.Yellow("*** Please note: Windows builds use mshtml which is only compatible with IE11. We strongly recommend only using IE11 when running 'wails serve'! For more information, please read https://wails.app/guides/windows/ ***")
}
2019-02-05 08:06:18 +11:00
logger.Green(">>>>> To connect, you will need to run '" + projectOptions.FrontEnd.Serve + "' in the '" + projectOptions.FrontEnd.Dir + "' directory <<<<<")
}()
2020-04-06 09:44:33 -05:00
location, err := filepath.Abs(filepath.Join("build", projectOptions.BinaryName))
2019-02-05 08:06:18 +11:00
if err != nil {
return err
}
logger.Yellow("Serving Application: " + location)
2021-05-14 14:15:06 +10:00
var args []string
if len(os.Args) > 2 {
2021-05-30 11:17:33 +10:00
foundArgSep := false
for index, arg := range os.Args[2:] {
if arg == "--" {
foundArgSep = true
continue
}
if foundArgSep {
args = os.Args[index:]
break
}
}
2021-05-14 14:15:06 +10:00
logger.Yellow("Passing arguments: %+v", args)
}
cmd := exec.Command(location, args...)
2019-02-05 08:06:18 +11:00
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
return nil
}
2020-04-04 13:08:42 -05:00
func ldFlags(po *ProjectOptions, buildMode string) string {
// Setup ld flags
ldflags := "-w -s "
if buildMode == BuildModeDebug {
ldflags = ""
}
// Add windows flags
if po.Platform == "windows" && buildMode == BuildModeProd {
ldflags += "-H windowsgui "
}
2020-10-29 20:18:52 +11:00
if po.UseFirebug {
ldflags += "-X github.com/wailsapp/wails/lib/renderer.UseFirebug=true "
}
2020-04-04 13:08:42 -05:00
ldflags += "-X github.com/wailsapp/wails.BuildMode=" + buildMode
2020-06-22 20:11:56 +10:00
// Add additional ldflags passed in via the `ldflags` cli flag
if len(po.LdFlags) > 0 {
ldflags += " " + po.LdFlags
}
2020-04-04 13:08:42 -05:00
// If we wish to generate typescript
if po.typescriptDefsFilename != "" {
cwd, err := os.Getwd()
if err == nil {
filename := filepath.Join(cwd, po.FrontEnd.Dir, po.typescriptDefsFilename)
ldflags += " -X github.com/wailsapp/wails/lib/binding.typescriptDefinitionFilename=" + filename
}
}
return ldflags
}
2021-03-27 21:06:02 +11:00
func getGitConfigValue(key string) (string, error) {
output, err := exec.Command("git", "config", "--get", "--null", key).Output()
// When using --null git appends a null character (\u0000) to the command output
return strings.TrimRight(string(output), "\u0000"), err
}