Support building Mac package

This commit is contained in:
Lea Anthony
2020-11-21 07:09:25 +11:00
parent ef8b58b208
commit 9e6ae4d762
12 changed files with 224 additions and 13 deletions

View File

@@ -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("")

View File

@@ -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

View File

@@ -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=

View File

@@ -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)

View File

@@ -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

View File

@@ -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()...)

View File

@@ -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

View 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>

View File

@@ -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() {}

View File

@@ -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
View File

@@ -0,0 +1,2 @@
appicon.png
info.plist

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB