mirror of
https://github.com/taigrr/wails.git
synced 2026-04-15 11:21:15 -07:00
Merge commit '25a157e6614739fbd4df1da3d11d46afe72ae9a8' as 'v2'
This commit is contained in:
340
v2/pkg/commands/build/base.go
Normal file
340
v2/pkg/commands/build/base.go
Normal file
@@ -0,0 +1,340 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/assetdb"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/fs"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/html"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/logger"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/project"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/shell"
|
||||
)
|
||||
|
||||
// BaseBuilder is the common builder struct
|
||||
type BaseBuilder struct {
|
||||
filesToDelete slicer.StringSlicer
|
||||
projectData *project.Project
|
||||
}
|
||||
|
||||
// NewBaseBuilder creates a new BaseBuilder
|
||||
func NewBaseBuilder() *BaseBuilder {
|
||||
result := &BaseBuilder{}
|
||||
return result
|
||||
}
|
||||
|
||||
// SetProjectData sets the project data for this builder
|
||||
func (b *BaseBuilder) SetProjectData(projectData *project.Project) {
|
||||
b.projectData = projectData
|
||||
}
|
||||
|
||||
func (b *BaseBuilder) addFileToDelete(filename string) {
|
||||
b.filesToDelete.Add(filename)
|
||||
}
|
||||
|
||||
func (b *BaseBuilder) fileExists(path string) bool {
|
||||
// if file doesn't exist, ignore
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// buildStaticAssets will iterate through the projects static directory and add all files
|
||||
// to the application wide asset database.
|
||||
func (b *BaseBuilder) buildStaticAssets(projectData *project.Project) error {
|
||||
|
||||
// Add trailing slash to Asset directory
|
||||
assetsDir := filepath.Join(projectData.Path, "assets") + "/"
|
||||
|
||||
assets := assetdb.NewAssetDB()
|
||||
if b.fileExists(assetsDir) {
|
||||
err := filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalisedPath := filepath.ToSlash(path)
|
||||
localPath := strings.TrimPrefix(normalisedPath, assetsDir)
|
||||
if len(localPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
if data, err := ioutil.ReadFile(filepath.Join(assetsDir, localPath)); err == nil {
|
||||
assets.AddAsset(localPath, data)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write assetdb out to root directory
|
||||
assetsDbFilename := fs.RelativePath("../../../assetsdb.go")
|
||||
b.addFileToDelete(assetsDbFilename)
|
||||
err := ioutil.WriteFile(assetsDbFilename, []byte(assets.Serialize("assets", "wails")), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BaseBuilder) convertFileToIntegerString(filename string) (string, error) {
|
||||
rawData, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.convertByteSliceToIntegerString(rawData), nil
|
||||
}
|
||||
|
||||
func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string {
|
||||
|
||||
// Create string builder
|
||||
var result strings.Builder
|
||||
|
||||
if len(data) > 0 {
|
||||
|
||||
// Loop over all but 1 bytes
|
||||
for i := 0; i < len(data)-1; i++ {
|
||||
result.WriteString(fmt.Sprintf("%v,", data[i]))
|
||||
}
|
||||
|
||||
result.WriteString(fmt.Sprintf("%v", data[len(data)-1]))
|
||||
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// CleanUp does post-build housekeeping
|
||||
func (b *BaseBuilder) CleanUp() {
|
||||
|
||||
// Delete all the files
|
||||
b.filesToDelete.Each(func(filename string) {
|
||||
|
||||
// if file doesn't exist, ignore
|
||||
if !b.fileExists(filename) {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete file. We ignore errors because these files will be overwritten
|
||||
// by the next build anyway.
|
||||
os.Remove(filename)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// CompileProject compiles the project
|
||||
func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
|
||||
// Default go build command
|
||||
commands := slicer.String([]string{"build"})
|
||||
|
||||
// Add the output type build tag
|
||||
commands.Add("-tags")
|
||||
commands.Add(options.OutputType)
|
||||
|
||||
// Strip binary in Production mode
|
||||
if options.Mode == Production {
|
||||
|
||||
// Different linker flags depending on platform
|
||||
commands.Add("-ldflags")
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
commands.Add("-w -s -H windowsgui")
|
||||
default:
|
||||
commands.Add("-w -s")
|
||||
}
|
||||
}
|
||||
|
||||
// Get application build directory
|
||||
appDir, err := getApplicationBuildDirectory(options, options.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.LDFlags != "" {
|
||||
commands.Add("-ldflags")
|
||||
commands.Add(options.LDFlags)
|
||||
}
|
||||
|
||||
// Set up output filename
|
||||
outputFilePath := filepath.Join(appDir, b.projectData.OutputFilename)
|
||||
commands.Add("-o")
|
||||
commands.Add(outputFilePath)
|
||||
|
||||
b.projectData.OutputFilename = strings.TrimPrefix(outputFilePath, options.ProjectData.Path)
|
||||
|
||||
// Create the command
|
||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
|
||||
// Set the directory
|
||||
cmd.Dir = b.projectData.Path
|
||||
|
||||
// Set GO111MODULE environment variable
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
|
||||
// Setup buffers
|
||||
var stdo, stde bytes.Buffer
|
||||
cmd.Stdout = &stdo
|
||||
cmd.Stderr = &stde
|
||||
|
||||
// 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 nil
|
||||
}
|
||||
|
||||
// NpmInstall runs "npm install" in the given directory
|
||||
func (b *BaseBuilder) NpmInstall(sourceDir string) error {
|
||||
return b.NpmInstallUsingCommand(sourceDir, "npm install")
|
||||
}
|
||||
|
||||
// NpmInstallUsingCommand runs the given install command in the specified npm project directory
|
||||
func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string) error {
|
||||
|
||||
packageJSON := filepath.Join(sourceDir, "package.json")
|
||||
|
||||
// Check package.json exists
|
||||
if !fs.FileExists(packageJSON) {
|
||||
return fmt.Errorf("unable to load package.json at '%s'", packageJSON)
|
||||
}
|
||||
|
||||
install := false
|
||||
|
||||
// Get the MD5 sum of package.json
|
||||
packageJSONMD5 := fs.MustMD5File(packageJSON)
|
||||
|
||||
// Check whether we need to npm install
|
||||
packageChecksumFile := filepath.Join(sourceDir, "package.json.md5")
|
||||
if fs.FileExists(packageChecksumFile) {
|
||||
// Compare checksums
|
||||
storedChecksum := fs.MustLoadString(packageChecksumFile)
|
||||
if storedChecksum != packageJSONMD5 {
|
||||
fs.MustWriteString(packageChecksumFile, packageJSONMD5)
|
||||
install = true
|
||||
}
|
||||
} else {
|
||||
install = true
|
||||
fs.MustWriteString(packageChecksumFile, packageJSONMD5)
|
||||
}
|
||||
|
||||
// Install if node_modules doesn't exist
|
||||
nodeModulesDir := filepath.Join(sourceDir, "node_modules")
|
||||
if !fs.DirExists(nodeModulesDir) {
|
||||
install = true
|
||||
}
|
||||
|
||||
// Shortcut installation
|
||||
if install == false {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Split up the InstallCommand and execute it
|
||||
cmd := strings.Split(installCommand, " ")
|
||||
stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
for _, l := range strings.Split(stdout, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
for _, l := range strings.Split(stderr, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NpmRun executes the npm target in the provided directory
|
||||
func (b *BaseBuilder) NpmRun(projectDir, buildTarget string, verbose bool) error {
|
||||
stdout, stderr, err := shell.RunCommand(projectDir, "npm", "run", buildTarget)
|
||||
if verbose || err != nil {
|
||||
for _, l := range strings.Split(stdout, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
for _, l := range strings.Split(stderr, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NpmRunWithEnvironment executes the npm target in the provided directory, with the given environment variables
|
||||
func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verbose bool, envvars []string) error {
|
||||
cmd := shell.CreateCommand(projectDir, "npm", "run", buildTarget)
|
||||
cmd.Env = append(os.Environ(), envvars...)
|
||||
var stdo, stde bytes.Buffer
|
||||
cmd.Stdout = &stdo
|
||||
cmd.Stderr = &stde
|
||||
err := cmd.Run()
|
||||
if verbose || err != nil {
|
||||
for _, l := range strings.Split(stdo.String(), "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
for _, l := range strings.Split(stde.String(), "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// BuildFrontend executes the `npm build` command for the frontend directory
|
||||
func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
|
||||
verbose := false
|
||||
|
||||
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.Writeln(" - No Install command. Skipping.")
|
||||
} else {
|
||||
// Do install if needed
|
||||
outputLogger.Writeln(" - Installing dependencies...")
|
||||
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a build command
|
||||
if b.projectData.BuildCommand == "" {
|
||||
outputLogger.Writeln(" - No Build command. Skipping.")
|
||||
// No - ignore
|
||||
return nil
|
||||
}
|
||||
|
||||
outputLogger.Writeln(" - Compiling Frontend Project")
|
||||
cmd := strings.Split(b.projectData.BuildCommand, " ")
|
||||
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
|
||||
if verbose || err != nil {
|
||||
for _, l := range strings.Split(stdout, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
for _, l := range strings.Split(stderr, "\n") {
|
||||
fmt.Printf(" %s\n", l)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractAssets gets the assets from the index.html file
|
||||
func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
|
||||
|
||||
// Read in html
|
||||
return html.NewAssetBundle(b.projectData.HTML)
|
||||
}
|
||||
133
v2/pkg/commands/build/build.go
Normal file
133
v2/pkg/commands/build/build.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/logger"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/project"
|
||||
)
|
||||
|
||||
// Mode is the type used to indicate the build modes
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Debug mode
|
||||
Debug Mode = iota
|
||||
// Production mode
|
||||
Production
|
||||
)
|
||||
|
||||
var modeMap = []string{"Debug", "Production"}
|
||||
|
||||
// Options contains all the build options as well as the project data
|
||||
type Options struct {
|
||||
LDFlags string // Optional flags to pass to linker
|
||||
Logger *logger.Logger // All output to the logger
|
||||
OutputType string // EG: desktop, server....
|
||||
Mode Mode // release or debug
|
||||
ProjectData *project.Project // The project data
|
||||
Pack bool // Create a package for the app after building
|
||||
Platform string // The platform to build for
|
||||
Compiler string // The compiler command to use
|
||||
}
|
||||
|
||||
// GetModeAsString returns the current mode as a string
|
||||
func GetModeAsString(mode Mode) string {
|
||||
return modeMap[mode]
|
||||
}
|
||||
|
||||
// Build the project!
|
||||
func Build(options *Options) (string, error) {
|
||||
|
||||
// Extract logger
|
||||
outputLogger := options.Logger
|
||||
|
||||
// Create a default logger if it doesn't exist
|
||||
if outputLogger == nil {
|
||||
outputLogger = logger.New()
|
||||
}
|
||||
|
||||
// Get working directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check platform
|
||||
validPlatforms := slicer.String([]string{"linux"})
|
||||
if !validPlatforms.Contains(options.Platform) {
|
||||
return "", fmt.Errorf("platform %s not supported", options.Platform)
|
||||
}
|
||||
|
||||
// Load project
|
||||
projectData, err := project.Load(cwd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
options.ProjectData = projectData
|
||||
|
||||
// Save the project type
|
||||
projectData.OutputType = options.OutputType
|
||||
|
||||
// Create builder
|
||||
var builder Builder
|
||||
|
||||
switch projectData.OutputType {
|
||||
case "desktop":
|
||||
builder = newDesktopBuilder()
|
||||
case "hybrid":
|
||||
builder = newHybridBuilder()
|
||||
case "server":
|
||||
builder = newServerBuilder()
|
||||
default:
|
||||
return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType)
|
||||
}
|
||||
|
||||
// Set up our clean up method
|
||||
defer builder.CleanUp()
|
||||
|
||||
// Initialise Builder
|
||||
builder.SetProjectData(projectData)
|
||||
|
||||
outputLogger.Writeln(" - Building Wails Frontend")
|
||||
err = builder.BuildFrontend(outputLogger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Build the base assets
|
||||
outputLogger.Writeln(" - Compiling Assets")
|
||||
err = builder.BuildRuntime(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = builder.BuildAssets(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Compile the application
|
||||
outputLogger.Write(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
|
||||
err = builder.CompileProject(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outputLogger.Writeln("done.")
|
||||
// Do we need to pack the app?
|
||||
if options.Pack {
|
||||
|
||||
outputLogger.Writeln(" - Packaging Application")
|
||||
|
||||
// TODO: Allow cross platform build
|
||||
err = packageProject(options, runtime.GOOS)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return projectData.OutputFilename, nil
|
||||
|
||||
}
|
||||
16
v2/pkg/commands/build/builder.go
Normal file
16
v2/pkg/commands/build/builder.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/leaanthony/wailsv2/v2/internal/logger"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/project"
|
||||
)
|
||||
|
||||
// Builder defines a builder that can build Wails applications
|
||||
type Builder interface {
|
||||
SetProjectData(projectData *project.Project)
|
||||
BuildAssets(*Options) error
|
||||
BuildFrontend(*logger.Logger) error
|
||||
BuildRuntime(*Options) error
|
||||
CompileProject(*Options) error
|
||||
CleanUp()
|
||||
}
|
||||
136
v2/pkg/commands/build/desktop.go
Normal file
136
v2/pkg/commands/build/desktop.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/leaanthony/wailsv2/v2/internal/fs"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/html"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/logger"
|
||||
)
|
||||
|
||||
// DesktopBuilder builds applications for the desktop
|
||||
type DesktopBuilder struct {
|
||||
*BaseBuilder
|
||||
}
|
||||
|
||||
func newDesktopBuilder() *DesktopBuilder {
|
||||
return &DesktopBuilder{
|
||||
BaseBuilder: NewBaseBuilder(),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildAssets builds the assets for the desktop application
|
||||
func (d *DesktopBuilder) BuildAssets(options *Options) error {
|
||||
var err error
|
||||
|
||||
// Get a list of assets from the HTML
|
||||
assets, err := d.BaseBuilder.ExtractAssets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build base assets (HTML/JS/CSS/etc)
|
||||
err = d.BuildBaseAssets(assets, options.Logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build static assets
|
||||
err = d.buildStaticAssets(d.projectData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildBaseAssets builds the assets for the desktop application
|
||||
func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, outputLogger *logger.Logger) error {
|
||||
var err error
|
||||
|
||||
outputLogger.Write(" - Embedding Assets...")
|
||||
|
||||
// Get target asset directory
|
||||
assetDir := fs.RelativePath("../../../internal/ffenestri")
|
||||
|
||||
// Dump assets as C
|
||||
assetsFile, err := assets.WriteToCFile(assetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.addFileToDelete(assetsFile)
|
||||
|
||||
// Process Icon
|
||||
err = d.processIcon(assetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputLogger.Writeln("done.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processIcon will copy a default icon if one doesn't exist, then, if
|
||||
// needed, will compile the icon
|
||||
func (d *DesktopBuilder) processIcon(assetDir string) error {
|
||||
|
||||
// Copy default icon if one doesn't exist
|
||||
iconFile := filepath.Join(d.projectData.Path, "icon.png")
|
||||
if !fs.FileExists(iconFile) {
|
||||
err := fs.CopyFile(defaultIconPath(), iconFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Compile Icon
|
||||
return d.compileIcon(assetDir, iconFile)
|
||||
}
|
||||
|
||||
// BuildRuntime builds the Wails javascript runtime and then converts it into a C file
|
||||
func (d *DesktopBuilder) BuildRuntime(options *Options) error {
|
||||
|
||||
outputLogger := options.Logger
|
||||
|
||||
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
||||
|
||||
if err := d.NpmInstall(sourceDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputLogger.Write(" - Embedding Runtime...")
|
||||
envvars := []string{"WAILSPLATFORM=" + options.Platform}
|
||||
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js")
|
||||
runtimeData, err := ioutil.ReadFile(wailsJS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputLogger.Writeln("done.")
|
||||
|
||||
// Convert to C structure
|
||||
runtimeC := `
|
||||
// runtime.c (c) 2019-Present Lea Anthony.
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file was auto-generated. DO NOT MODIFY.
|
||||
const unsigned char runtime[]={`
|
||||
for _, b := range runtimeData {
|
||||
runtimeC += fmt.Sprintf("0x%x, ", b)
|
||||
}
|
||||
runtimeC += "0x00};"
|
||||
|
||||
// Save file
|
||||
outputFile := fs.RelativePath("../../../internal/ffenestri/runtime.c")
|
||||
|
||||
if err := ioutil.WriteFile(outputFile, []byte(runtimeC), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
8
v2/pkg/commands/build/desktop_default.go
Normal file
8
v2/pkg/commands/build/desktop_default.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !linux
|
||||
|
||||
package build
|
||||
|
||||
// This is used when there is no compilation to be done for the asset
|
||||
func (d *DesktopBuilder) compileIcon(assetDir string, iconFile string) error {
|
||||
return nil
|
||||
}
|
||||
54
v2/pkg/commands/build/desktop_linux.go
Normal file
54
v2/pkg/commands/build/desktop_linux.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// +build linux
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/xyproto/xpm"
|
||||
)
|
||||
|
||||
// compileIcon will compile the icon found at <projectdir>/icon.png into the application
|
||||
func (d *DesktopBuilder) compileIcon(assetDir string, iconFile string) error {
|
||||
|
||||
// Load icon into a databuffer
|
||||
targetFilename := "icon"
|
||||
targetFile := filepath.Join(assetDir, targetFilename+".c")
|
||||
|
||||
d.addFileToDelete(targetFile)
|
||||
|
||||
// Create a new XPM encoder
|
||||
enc := xpm.NewEncoder(targetFilename)
|
||||
|
||||
// Open the PNG file
|
||||
f, err := os.Open(iconFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := png.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
var buf strings.Builder
|
||||
|
||||
// Generate and output the XPM data
|
||||
err = enc.Encode(&buf, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Massage the output so we can extern reference it
|
||||
output := buf.String()
|
||||
output = strings.Replace(output, "static char", "const char", 1)
|
||||
|
||||
// save icon.c
|
||||
err = ioutil.WriteFile(targetFile, []byte(output), 0755)
|
||||
|
||||
return err
|
||||
}
|
||||
101
v2/pkg/commands/build/hybrid.go
Normal file
101
v2/pkg/commands/build/hybrid.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/leaanthony/wailsv2/v2/internal/project"
|
||||
)
|
||||
|
||||
// HybridBuilder builds applications as a server
|
||||
type HybridBuilder struct {
|
||||
*BaseBuilder
|
||||
desktop *DesktopBuilder
|
||||
server *ServerBuilder
|
||||
}
|
||||
|
||||
func newHybridBuilder() Builder {
|
||||
result := &HybridBuilder{
|
||||
BaseBuilder: NewBaseBuilder(),
|
||||
desktop: newDesktopBuilder(),
|
||||
server: newServerBuilder(),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BuildAssets builds the assets for the desktop application
|
||||
func (b *HybridBuilder) BuildAssets(options *Options) error {
|
||||
var err error
|
||||
|
||||
// Build base assets (HTML/JS/CSS/etc)
|
||||
err = b.BuildBaseAssets(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Build static assets
|
||||
err = b.buildStaticAssets(b.projectData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildAssets builds the assets for the desktop application
|
||||
func (b *HybridBuilder) BuildBaseAssets(options *Options) error {
|
||||
|
||||
assets, err := b.BaseBuilder.ExtractAssets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.desktop.BuildBaseAssets(assets, options.Logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.server.BuildBaseAssets(assets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build desktop static assets
|
||||
err = b.desktop.buildStaticAssets(b.projectData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build server static assets
|
||||
err = b.server.buildStaticAssets(b.projectData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *HybridBuilder) BuildRuntime(options *Options) error {
|
||||
err := b.desktop.BuildRuntime(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.server.BuildRuntime(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *HybridBuilder) SetProjectData(projectData *project.Project) {
|
||||
b.BaseBuilder.SetProjectData(projectData)
|
||||
b.desktop.SetProjectData(projectData)
|
||||
b.server.SetProjectData(projectData)
|
||||
}
|
||||
|
||||
func (b *HybridBuilder) CompileProject(options *Options) error {
|
||||
return b.BaseBuilder.CompileProject(options)
|
||||
}
|
||||
|
||||
func (b *HybridBuilder) CleanUp() {
|
||||
b.desktop.CleanUp()
|
||||
b.server.CleanUp()
|
||||
}
|
||||
BIN
v2/pkg/commands/build/internal/packager/icon1024.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
v2/pkg/commands/build/internal/packager/icon128.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
v2/pkg/commands/build/internal/packager/icon256.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
v2/pkg/commands/build/internal/packager/icon32.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
BIN
v2/pkg/commands/build/internal/packager/icon512.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
v2/pkg/commands/build/internal/packager/icon64.png
Normal file
BIN
v2/pkg/commands/build/internal/packager/icon64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
3
v2/pkg/commands/build/internal/packager/linux/AppRun
Normal file
3
v2/pkg/commands/build/internal/packager/linux/AppRun
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "$(dirname "$0")"
|
||||
exec ./{{.OutputFilename}}
|
||||
@@ -0,0 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Exec={{.Name}}
|
||||
Name={{.Name}}
|
||||
Icon={{.Name}}
|
||||
Categories=Utility;
|
||||
73
v2/pkg/commands/build/packager.go
Normal file
73
v2/pkg/commands/build/packager.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/leaanthony/wailsv2/v2/internal/fs"
|
||||
)
|
||||
|
||||
// PackageProject packages the application
|
||||
func packageProject(options *Options, platform string) error {
|
||||
|
||||
var err error
|
||||
switch platform {
|
||||
case "linux":
|
||||
err = packageLinuxApplication(options)
|
||||
default:
|
||||
err = fmt.Errorf("packing not supported for %s yet", platform)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gets (and creates) the platform/target build directory
|
||||
func getApplicationBuildDirectory(options *Options, platform string) (string, error) {
|
||||
buildDirectory := filepath.Join(options.ProjectData.Path, "build", platform, options.OutputType)
|
||||
|
||||
// Clear out old builds
|
||||
if fs.DirExists(buildDirectory) {
|
||||
err := os.RemoveAll(buildDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Create clean directory
|
||||
err := os.MkdirAll(buildDirectory, 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buildDirectory, nil
|
||||
}
|
||||
|
||||
func copyFileToBuildDirectory() {}
|
||||
|
||||
// Gets (and creates) the build base directory
|
||||
func getBuildBaseDirectory(options *Options) (string, error) {
|
||||
buildDirectory := filepath.Join(options.ProjectData.Path, "build")
|
||||
if !fs.DirExists(buildDirectory) {
|
||||
err := os.MkdirAll(buildDirectory, 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return buildDirectory, nil
|
||||
}
|
||||
|
||||
// Gets the path to the default icon
|
||||
func defaultIconPath() string {
|
||||
return fs.RelativePath("internal/packager/icon64.png")
|
||||
}
|
||||
|
||||
// Gets the platform dependent package assets directory
|
||||
func getPackageAssetsDirectory() string {
|
||||
return fs.RelativePath("internal/packager", runtime.GOOS)
|
||||
}
|
||||
134
v2/pkg/commands/build/packager_linux.go
Normal file
134
v2/pkg/commands/build/packager_linux.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/fs"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/shell"
|
||||
)
|
||||
|
||||
func deleteLinuxPackFiles(appDirBase string) {
|
||||
// Delete appdir
|
||||
appDir := filepath.Join(appDirBase, "AppDir")
|
||||
os.RemoveAll(appDir)
|
||||
}
|
||||
|
||||
func packageLinuxApplication(options *Options) error {
|
||||
|
||||
// Check we have AppImage tools
|
||||
|
||||
// Create AppImage build directory
|
||||
buildDirectory, err := getApplicationBuildDirectory(options, "linux")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer deleteLinuxPackFiles(buildDirectory)
|
||||
|
||||
// Get the name of the application and ensure we lower+kebab case it
|
||||
name := filepath.Base(options.ProjectData.OutputFilename)
|
||||
|
||||
// Calculate asset directory
|
||||
assetDir := getPackageAssetsDirectory()
|
||||
|
||||
// Copy default icon if one doesn't exist
|
||||
baseBuildDirectory, err := getBuildBaseDirectory(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iconFile := filepath.Join(baseBuildDirectory, "icon.png")
|
||||
if !fs.FileExists(iconFile) {
|
||||
err = fs.CopyFile(defaultIconPath(), iconFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Icon
|
||||
targetIcon := filepath.Join(buildDirectory, name+".png")
|
||||
err = fs.CopyFile(iconFile, targetIcon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy app.desktop
|
||||
dotDesktopFile := filepath.Join(baseBuildDirectory, "linux", name+".desktop")
|
||||
if !fs.FileExists(dotDesktopFile) {
|
||||
bytes, err := ioutil.ReadFile(filepath.Join(assetDir, "app.desktop"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appDesktop := string(bytes)
|
||||
appDesktop = strings.ReplaceAll(appDesktop, `{{.Name}}`, name)
|
||||
err = ioutil.WriteFile(dotDesktopFile, []byte(appDesktop), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy AppRun file
|
||||
// targetFilename = filepath.Join(buildDirectory, "AppRun")
|
||||
// if !fs.FileExists(targetFilename) {
|
||||
// bytes, err := ioutil.ReadFile(filepath.Join(assetDir, "AppRun"))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// appRun := string(bytes)
|
||||
// appRun = strings.ReplaceAll(appRun, `{{.OutputFilename}}`, name)
|
||||
|
||||
// err = ioutil.WriteFile(targetFilename, []byte(appRun), 0644)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// Copy Binary
|
||||
sourceFile := filepath.Join(options.ProjectData.Path, options.ProjectData.OutputFilename)
|
||||
targetFile := filepath.Join(buildDirectory, options.ProjectData.OutputFilename)
|
||||
err = fs.CopyFile(sourceFile, targetFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(targetFile, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/** Pack App **/
|
||||
|
||||
// Make file executable
|
||||
// Set environment variable: OUTPUT=outputfilename
|
||||
command := shell.NewCommand("linuxdeploy-x86_64.AppImage")
|
||||
command.Dir(buildDirectory)
|
||||
|
||||
argslice := slicer.String()
|
||||
argslice.Add("--appdir", "AppDir")
|
||||
argslice.Add("-d", filepath.Join("..", name+".desktop"))
|
||||
argslice.Add("-i", name+".png")
|
||||
argslice.Add("-e", name)
|
||||
argslice.Add("--output", "appimage")
|
||||
command.AddArgs(argslice.AsSlice())
|
||||
|
||||
command.Env("OUTPUT", name+".AppImage")
|
||||
|
||||
err = command.Run()
|
||||
if err != nil {
|
||||
println(command.Stdout())
|
||||
println(command.Stderr())
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy app to project dir
|
||||
|
||||
println(buildDirectory)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDirectory(directory string) {
|
||||
os.RemoveAll(directory)
|
||||
}
|
||||
11
v2/pkg/commands/build/server-assetdb.go.template
Normal file
11
v2/pkg/commands/build/server-assetdb.go.template
Normal file
@@ -0,0 +1,11 @@
|
||||
package webserver
|
||||
|
||||
// This sets up the assets that are defined in `webassets.go`
|
||||
func init() {
|
||||
html = []byte{$HTMLBYTES$}
|
||||
js = []byte{$JSBYTES$}
|
||||
css = []byte{$CSSBYTES$}
|
||||
wailsjs = []byte{$WAILSJSBYTES$}
|
||||
jsurl = []byte{$JSURLBYTES$}
|
||||
cssurl = []byte{$CSSURLBYTES$}
|
||||
}
|
||||
111
v2/pkg/commands/build/server.go
Normal file
111
v2/pkg/commands/build/server.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/wailsv2/v2/internal/fs"
|
||||
"github.com/leaanthony/wailsv2/v2/internal/html"
|
||||
)
|
||||
|
||||
// ServerBuilder builds applications as a server
|
||||
type ServerBuilder struct {
|
||||
*BaseBuilder
|
||||
}
|
||||
|
||||
func newServerBuilder() *ServerBuilder {
|
||||
result := &ServerBuilder{
|
||||
BaseBuilder: NewBaseBuilder(),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BuildAssets builds the assets for the desktop application
|
||||
func (s *ServerBuilder) BuildAssets(options *Options) error {
|
||||
var err error
|
||||
|
||||
assets, err := s.BaseBuilder.ExtractAssets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build embedded assets (HTML/JS/CSS/etc)
|
||||
err = s.BuildBaseAssets(assets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build static assets
|
||||
err = s.buildStaticAssets(s.projectData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildBaseAssets builds the base assets
|
||||
func (s *ServerBuilder) BuildBaseAssets(assets *html.AssetBundle) error {
|
||||
db, err := assets.ConvertToAssetDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch, update, and reinject index.html
|
||||
index, err := db.Read("index.html")
|
||||
if err != nil {
|
||||
return fmt.Errorf(`Failed to locate "index.html"`)
|
||||
}
|
||||
splits := strings.Split(string(index), "</body>")
|
||||
if len(splits) != 2 {
|
||||
return fmt.Errorf("Unable to locate a </body> tag in your frontend/index.html")
|
||||
}
|
||||
injectScript := `<script defer src="/wails.js"></script><script defer src="/bindings.js"></script>`
|
||||
result := []string{}
|
||||
result = append(result, splits[0])
|
||||
result = append(result, injectScript)
|
||||
result = append(result, "</body>")
|
||||
result = append(result, splits[1])
|
||||
|
||||
db.Remove("index.html") // Remove the non-prefixed index.html
|
||||
db.AddAsset("/index.html", []byte(strings.Join(result, "")))
|
||||
|
||||
// Add wails.js
|
||||
wailsjsPath := fs.RelativePath("../../../internal/runtime/assets/server.js")
|
||||
if rawData, err := ioutil.ReadFile(wailsjsPath); err == nil {
|
||||
db.AddAsset("/wails.js", rawData)
|
||||
}
|
||||
|
||||
targetFile := fs.RelativePath("../../../internal/webserver/webassetsdb.go")
|
||||
s.addFileToDelete(targetFile)
|
||||
f, err := os.Create(targetFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
f.WriteString(db.Serialize("db", "webserver"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildRuntime builds the javascript runtime used by the HTML client to connect to the websocket
|
||||
func (s *ServerBuilder) BuildRuntime(options *Options) error {
|
||||
|
||||
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
||||
if err := s.NpmInstall(sourceDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options.Logger.Write(" - Embedding Runtime...")
|
||||
envvars := []string{"WAILSPLATFORM=" + options.Platform}
|
||||
var err error
|
||||
if err = s.NpmRunWithEnvironment(sourceDir, "build:server", false, envvars); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options.Logger.Writeln("done.")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user