5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 22:31:06 +08:00
wails/v2/pkg/commands/build/packager.go
2021-11-13 17:06:48 -08:00

326 lines
7.0 KiB
Go

package build
import (
"fmt"
"image"
"os"
"path"
"path/filepath"
"runtime"
"github.com/leaanthony/winicon"
"github.com/tc-hib/winres"
"github.com/jackmordaunt/icns"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/internal/fs"
)
// PackageProject packages the application
func packageProject(options *Options, platform string) error {
var err error
switch platform {
case "darwin":
err = packageApplicationForDarwin(options)
case "windows":
err = packageApplicationForWindows(options)
case "linux":
err = packageApplicationForLinux(options)
default:
err = fmt.Errorf("packing not supported for %s yet", platform)
}
if err != nil {
return err
}
return nil
}
// cleanBuildDirectory will remove an existing build directory and recreate it
func cleanBuildDirectory(options *Options) error {
buildDirectory := options.BuildDirectory
// 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 nil
}
// 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 platform dependent package assets directory
func getPackageAssetsDirectory() string {
return fs.RelativePath("internal/packager", runtime.GOOS)
}
func packageApplicationForDarwin(options *Options) error {
var err error
// Create directory structure
bundlename := options.ProjectData.Name + ".app"
contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents")
exeDir := filepath.Join(contentsDirectory, "/MacOS")
err = fs.MkDirs(exeDir, 0755)
if err != nil {
return err
}
resourceDir := filepath.Join(contentsDirectory, "/Resources")
err = fs.MkDirs(resourceDir, 0755)
if err != nil {
return err
}
// 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 = processApplicationIcon(resourceDir, options.ProjectData.Path)
if err != nil {
return err
}
options.CompiledBinary = packedBinaryPath
return nil
}
func processPList(options *Options, contentsDirectory string) error {
// Check if plist already exists in project dir
plistFileDir := filepath.Join(options.ProjectData.Path, "build", "darwin")
plistFile := filepath.Join(plistFileDir, "Info.plist")
// If the file doesn't exist, generate it
if !fs.FileExists(plistFile) {
err := buildassets.RegeneratePlist(plistFileDir, options.ProjectData.Name)
if err != nil {
return err
}
}
// Copy it to the contents directory
targetFile := filepath.Join(contentsDirectory, "Info.plist")
return fs.CopyFile(plistFile, targetFile)
}
func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
appIcon := filepath.Join(iconsDir, "appicon.png")
// Install default icon if one doesn't exist
if !fs.FileExists(appIcon) {
// No - Install default icon
err = buildassets.RegenerateAppIcon(appIcon)
if err != nil {
return
}
}
tgtBundle := path.Join(resourceDir, "iconfile.icns")
imageFile, err := os.Open(appIcon)
if err != nil {
return err
}
defer func() {
err = imageFile.Close()
if err == nil {
return
}
}()
srcImg, _, err := image.Decode(imageFile)
if err != nil {
return err
}
dest, err := os.Create(tgtBundle)
if err != nil {
return err
}
defer func() {
err = dest.Close()
if err == nil {
return
}
}()
return icns.Encode(dest, srcImg)
}
func packageApplicationForWindows(options *Options) error {
// Generate icon
var err error
err = generateIcoFile(options)
if err != nil {
return err
}
// Ensure Manifest is present
err = generateManifest(options)
if err != nil {
return err
}
// Create syso file
err = compileResources(options)
if err != nil {
return err
}
return nil
}
func packageApplicationForLinux(options *Options) error {
// Generate icon
//var err error
//err = generateIcoFile(options)
//if err != nil {
// return err
//}
//
//// Ensure Manifest is present
//err = generateManifest(options)
//if err != nil {
// return err
//}
//
//// Create syso file
//err = compileResources(options)
//if err != nil {
// return err
//}
return nil
}
func generateManifest(options *Options) error {
filename := options.ProjectData.Name + ".exe.manifest"
manifestFile := filepath.Join(options.ProjectData.Path, "build", "windows", filename)
if !fs.FileExists(manifestFile) {
return buildassets.RegenerateManifest(manifestFile)
}
return nil
}
func generateIcoFile(options *Options) error {
// Check ico file exists already
icoFile := filepath.Join(options.ProjectData.Path, "build", "windows", "icon.ico")
if !fs.FileExists(icoFile) {
// Check icon exists
appicon := filepath.Join(options.ProjectData.Path, "build", "appicon.png")
if !fs.FileExists(appicon) {
return fmt.Errorf("application icon missing: %s", appicon)
}
// Load icon
input, err := os.Open(appicon)
if err != nil {
return err
}
output, err := os.OpenFile(icoFile, os.O_CREATE, 0644)
if err != nil {
return err
}
err = winicon.GenerateIcon(input, output, []int{256, 128, 64, 48, 32, 16})
if err != nil {
return err
}
}
return nil
}
func compileResources(options *Options) error {
currentDir, err := os.Getwd()
if err != nil {
return err
}
defer func() {
os.Chdir(currentDir)
}()
windowsDir := filepath.Join(options.ProjectData.Path, "build", "windows")
err = os.Chdir(windowsDir)
if err != nil {
return err
}
rs := winres.ResourceSet{}
icon := filepath.Join(windowsDir, "icon.ico")
iconFile, err := os.Open(icon)
if err != nil {
return err
}
defer iconFile.Close()
ico, err := winres.LoadICO(iconFile)
if err != nil {
return err
}
err = rs.SetIcon(winres.RT_ICON, ico)
if err != nil {
return err
}
ManifestFilename := options.ProjectData.Name + ".exe.manifest"
manifestData, err := os.ReadFile(ManifestFilename)
xmlData, err := winres.AppManifestFromXML(manifestData)
if err != nil {
return err
}
rs.SetManifest(xmlData)
targetFile := filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso")
fout, err := os.Create(targetFile)
if err != nil {
return err
}
defer fout.Close()
archs := map[string]winres.Arch{
"amd64": winres.ArchAMD64,
}
targetArch, supported := archs[options.Arch]
if !supported {
return fmt.Errorf("arch '%s' not supported", options.Arch)
}
err = rs.WriteObject(fout, targetArch)
if err != nil {
return err
}
return nil
}