mirror of
https://github.com/taigrr/wails.git
synced 2026-04-02 05:08:54 -07:00
Support building Mac package
This commit is contained in:
@@ -32,7 +32,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Setup pack flag
|
||||
pack := false
|
||||
command.BoolFlag("pack", "Create a platform specific package", &pack)
|
||||
command.BoolFlag("package", "Create a platform specific package", &pack)
|
||||
|
||||
compilerCommand := "go"
|
||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
||||
@@ -103,6 +103,7 @@ func doBuild(buildOptions *build.Options) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Output stats
|
||||
elapsed := time.Since(start)
|
||||
buildOptions.Logger.Println("")
|
||||
|
||||
@@ -7,10 +7,12 @@ require (
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
github.com/leaanthony/gosod v0.0.4
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/matryer/is v1.4.0
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/pkg/errors v0.9.1
|
||||
|
||||
@@ -33,6 +33,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
|
||||
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
@@ -58,6 +60,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
|
||||
@@ -36,6 +36,24 @@ func Mkdir(dirname string) error {
|
||||
return os.Mkdir(dirname, 0755)
|
||||
}
|
||||
|
||||
// MkDirs creates the given nested directories.
|
||||
// Returns error on failure
|
||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
var perms os.FileMode
|
||||
perms = 0700
|
||||
if len(mode) == 1 {
|
||||
perms = mode[0]
|
||||
}
|
||||
return os.MkdirAll(fullPath, perms)
|
||||
}
|
||||
|
||||
// MoveFile attempts to move the source file to the target
|
||||
// Target is a fully qualified path to a file *name*, not a
|
||||
// directory
|
||||
func MoveFile(source string, target string) error {
|
||||
return os.Rename(source, target)
|
||||
}
|
||||
|
||||
// DeleteFile will delete the given file
|
||||
func DeleteFile(filename string) error {
|
||||
return os.Remove(filename)
|
||||
|
||||
@@ -33,6 +33,15 @@ type Project struct {
|
||||
|
||||
// The platform to target
|
||||
Platform string
|
||||
|
||||
// The application author
|
||||
Author Author
|
||||
}
|
||||
|
||||
// Author stores details about the application author
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// Load the project from the current working directory
|
||||
|
||||
@@ -165,7 +165,8 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
}
|
||||
|
||||
// Get application build directory
|
||||
appDir, err := getApplicationBuildDirectory(options, options.Platform)
|
||||
appDir := options.BuildDirectory
|
||||
err := cleanBuildDirectory(options, options.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -180,11 +181,12 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
if outputFile == "" {
|
||||
outputFile = b.projectData.OutputFilename
|
||||
}
|
||||
outputFilePath := filepath.Join(appDir, outputFile)
|
||||
compiledBinary := filepath.Join(appDir, outputFile)
|
||||
commands.Add("-o")
|
||||
commands.Add(outputFilePath)
|
||||
commands.Add(compiledBinary)
|
||||
|
||||
b.projectData.OutputFilename = strings.TrimPrefix(outputFilePath, options.ProjectData.Path)
|
||||
b.projectData.OutputFilename = strings.TrimPrefix(compiledBinary, options.ProjectData.Path)
|
||||
options.CompiledBinary = compiledBinary
|
||||
|
||||
// Create the command
|
||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
|
||||
@@ -3,6 +3,7 @@ package build
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
@@ -34,6 +35,8 @@ type Options struct {
|
||||
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
|
||||
}
|
||||
|
||||
// GetModeAsString returns the current mode as a string
|
||||
@@ -66,6 +69,9 @@ func Build(options *Options) (string, error) {
|
||||
}
|
||||
options.ProjectData = projectData
|
||||
|
||||
// Calculate build dir
|
||||
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
|
||||
|
||||
// Save the project type
|
||||
projectData.OutputType = options.OutputType
|
||||
|
||||
|
||||
12
v2/pkg/commands/build/internal/packager/darwin/info.plist
Normal file
12
v2/pkg/commands/build/internal/packager/darwin/info.plist
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleName</key><string>{{.Title}}</string>
|
||||
<key>CFBundleExecutable</key><string>{{.Exe}}</string>
|
||||
<key>CFBundleIdentifier</key><string>{{.PackageID}}</string>
|
||||
<key>CFBundleVersion</key><string>{{.Version}}</string>
|
||||
<key>CFBundleGetInfoString</key><string>Built by {{.Author}} using Wails (https://wails.app)</string>
|
||||
<key>CFBundleShortVersionString</key><string>{{.Version}}</string>
|
||||
<key>CFBundleIconFile</key><string>iconfile</string>
|
||||
<key>NSHighResolutionCapable</key><string>true</string>
|
||||
</dict></plist>
|
||||
@@ -14,7 +14,7 @@ func packageProject(options *Options, platform string) error {
|
||||
|
||||
var err error
|
||||
switch platform {
|
||||
case "linux":
|
||||
case "linux", "darwin":
|
||||
err = packageApplication(options)
|
||||
default:
|
||||
err = fmt.Errorf("packing not supported for %s yet", platform)
|
||||
@@ -27,25 +27,26 @@ func packageProject(options *Options, platform string) error {
|
||||
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)
|
||||
// cleanBuildDirectory will remove an existing build directory and recreate it
|
||||
func cleanBuildDirectory(options *Options, platform string) error {
|
||||
|
||||
buildDirectory := options.BuildDirectory
|
||||
|
||||
// Clear out old builds
|
||||
if fs.DirExists(buildDirectory) {
|
||||
err := os.RemoveAll(buildDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create clean directory
|
||||
err := os.MkdirAll(buildDirectory, 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
return buildDirectory, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileToBuildDirectory() {}
|
||||
|
||||
@@ -1,6 +1,160 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/jackmordaunt/icns"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
)
|
||||
|
||||
func packageApplication(options *Options) error {
|
||||
// TBD
|
||||
|
||||
var err error
|
||||
|
||||
// Create directory structure
|
||||
bundlename := options.ProjectData.Name + ".app"
|
||||
|
||||
contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents")
|
||||
exeDir := filepath.Join(contentsDirectory, "/MacOS")
|
||||
fs.MkDirs(exeDir, 0755)
|
||||
resourceDir := filepath.Join(contentsDirectory, "/Resources")
|
||||
fs.MkDirs(resourceDir, 0755)
|
||||
|
||||
// Copy binary
|
||||
packedBinaryPath := filepath.Join(exeDir, options.ProjectData.Name)
|
||||
err = fs.MoveFile(options.CompiledBinary, packedBinaryPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename)
|
||||
}
|
||||
|
||||
// Generate info.plist
|
||||
err = processPList(options, contentsDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate Icons
|
||||
err = processIcon(resourceDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processPList(options *Options, contentsDirectory string) error {
|
||||
// Check if plist already exists in project dir
|
||||
plistFile, err := fs.RelativeToCwd("info.plist")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the file doesn't exist, generate it
|
||||
if !fs.FileExists(plistFile) {
|
||||
err = generateDefaultPlist(options, plistFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy it to the contents directory
|
||||
targetFile := filepath.Join(contentsDirectory, "info.plist")
|
||||
return fs.CopyFile(plistFile, targetFile)
|
||||
}
|
||||
|
||||
func generateDefaultPlist(options *Options, targetPlistFile string) error {
|
||||
name := defaultString(options.ProjectData.Name, "WailsTest")
|
||||
exe := defaultString(options.OutputFile, name)
|
||||
version := "1.0.0"
|
||||
author := defaultString(options.ProjectData.Author.Name, "Anonymous")
|
||||
packageID := strings.Join([]string{"wails", name, version}, ".")
|
||||
plistData := newPlistData(name, exe, packageID, version, author)
|
||||
|
||||
tmpl := template.New("infoPlist")
|
||||
plistTemplate := fs.RelativePath("./internal/packager/darwin/info.plist")
|
||||
infoPlist, err := ioutil.ReadFile(plistTemplate)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot open plist template")
|
||||
}
|
||||
tmpl.Parse(string(infoPlist))
|
||||
|
||||
// Write the template to a buffer
|
||||
var tpl bytes.Buffer
|
||||
err = tmpl.Execute(&tpl, plistData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the file
|
||||
return ioutil.WriteFile(targetPlistFile, tpl.Bytes(), 0644)
|
||||
}
|
||||
|
||||
func defaultString(val string, defaultVal string) string {
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
type plistData struct {
|
||||
Title string
|
||||
Exe string
|
||||
PackageID string
|
||||
Version string
|
||||
Author string
|
||||
}
|
||||
|
||||
func newPlistData(title, exe, packageID, version, author string) *plistData {
|
||||
return &plistData{
|
||||
Title: title,
|
||||
Exe: exe,
|
||||
Version: version,
|
||||
PackageID: packageID,
|
||||
Author: author,
|
||||
}
|
||||
}
|
||||
|
||||
func processIcon(resourceDir string) error {
|
||||
|
||||
appIcon, err := fs.RelativeToCwd("appicon.png")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Install default icon if one doesn't exist
|
||||
if !fs.FileExists(appIcon) {
|
||||
// No - Install default icon
|
||||
defaultIcon := fs.RelativePath("./internal/packager/icon1024.png")
|
||||
err := fs.CopyFile(defaultIcon, appIcon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tgtBundle := path.Join(resourceDir, "iconfile.icns")
|
||||
imageFile, err := os.Open(appIcon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer imageFile.Close()
|
||||
srcImg, _, err := image.Decode(imageFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
dest, err := os.Create(tgtBundle)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
defer dest.Close()
|
||||
return icns.Encode(dest, srcImg)
|
||||
}
|
||||
|
||||
2
v2/test/kitchensink/.gitignore
vendored
Normal file
2
v2/test/kitchensink/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
appicon.png
|
||||
info.plist
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.6 KiB |
Reference in New Issue
Block a user