mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 08:29:40 +08:00

* fix icon issues with windows when project name contains spaces * add comment * add to change log --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
302 lines
6.8 KiB
Go
302 lines
6.8 KiB
Go
package build
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/leaanthony/winicon"
|
|
"github.com/tc-hib/winres"
|
|
"github.com/tc-hib/winres/version"
|
|
"github.com/wailsapp/wails/v2/internal/project"
|
|
|
|
"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
|
|
}
|
|
|
|
// cleanBinDirectory will remove an existing bin directory and recreate it
|
|
func cleanBinDirectory(options *Options) error {
|
|
buildDirectory := options.BinDirectory
|
|
|
|
// Clear out old builds
|
|
if fs.DirExists(buildDirectory) {
|
|
err := os.RemoveAll(buildDirectory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create clean directory
|
|
err := os.MkdirAll(buildDirectory, 0o700)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func packageApplicationForDarwin(options *Options) error {
|
|
var err error
|
|
|
|
// Create directory structure
|
|
bundlename := options.BundleName
|
|
if bundlename == "" {
|
|
bundlename = options.ProjectData.Name + ".app"
|
|
}
|
|
|
|
contentsDirectory := filepath.Join(options.BinDirectory, bundlename, "/Contents")
|
|
exeDir := filepath.Join(contentsDirectory, "/MacOS")
|
|
err = fs.MkDirs(exeDir, 0o755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resourceDir := filepath.Join(contentsDirectory, "/Resources")
|
|
err = fs.MkDirs(resourceDir, 0o755)
|
|
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 App Icon
|
|
err = processDarwinIcon(options.ProjectData, "appicon", resourceDir, "iconfile")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate FileAssociation Icons
|
|
for _, fileAssociation := range options.ProjectData.Info.FileAssociations {
|
|
err = processDarwinIcon(options.ProjectData, fileAssociation.IconName, resourceDir, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
options.CompiledBinary = packedBinaryPath
|
|
|
|
return nil
|
|
}
|
|
|
|
func processPList(options *Options, contentsDirectory string) error {
|
|
sourcePList := "Info.plist"
|
|
if options.Mode == Dev {
|
|
// Use Info.dev.plist if using build mode
|
|
sourcePList = "Info.dev.plist"
|
|
}
|
|
|
|
// Read the resolved BuildAssets file and copy it to the destination
|
|
content, err := buildassets.ReadFileWithProjectData(options.ProjectData, "darwin/"+sourcePList)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
targetFile := filepath.Join(contentsDirectory, "Info.plist")
|
|
return os.WriteFile(targetFile, content, 0o644)
|
|
}
|
|
|
|
func processDarwinIcon(projectData *project.Project, iconName string, resourceDir string, destIconName string) (err error) {
|
|
appIcon, err := buildassets.ReadFile(projectData, iconName+".png")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcImg, _, err := image.Decode(bytes.NewBuffer(appIcon))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if destIconName == "" {
|
|
destIconName = iconName
|
|
}
|
|
|
|
tgtBundle := filepath.Join(resourceDir, destIconName+".icns")
|
|
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 app icon
|
|
var err error
|
|
err = generateIcoFile(options, "appicon", "icon")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate FileAssociation Icons
|
|
for _, fileAssociation := range options.ProjectData.Info.FileAssociations {
|
|
err = generateIcoFile(options, fileAssociation.IconName, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create syso file
|
|
err = compileResources(options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func packageApplicationForLinux(_ *Options) error {
|
|
return nil
|
|
}
|
|
|
|
func generateIcoFile(options *Options, iconName string, destIconName string) error {
|
|
content, err := buildassets.ReadFile(options.ProjectData, iconName+".png")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if destIconName == "" {
|
|
destIconName = iconName
|
|
}
|
|
|
|
// Check ico file exists already
|
|
icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/"+destIconName+".ico")
|
|
if !fs.FileExists(icoFile) {
|
|
if dir := filepath.Dir(icoFile); !fs.DirExists(dir) {
|
|
if err := fs.MkDirs(dir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer output.Close()
|
|
|
|
err = winicon.GenerateIcon(bytes.NewBuffer(content), 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.GetBuildDir(), "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 fmt.Errorf("couldn't load icon from icon.ico: %w", err)
|
|
}
|
|
err = rs.SetIcon(winres.RT_ICON, ico)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
manifestData, err := buildassets.ReadFileWithProjectData(options.ProjectData, "windows/wails.exe.manifest")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
xmlData, err := winres.AppManifestFromXML(manifestData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rs.SetManifest(xmlData)
|
|
|
|
versionInfo, err := buildassets.ReadFileWithProjectData(options.ProjectData, "windows/info.json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(versionInfo) != 0 {
|
|
var v version.Info
|
|
if err := v.UnmarshalJSON(versionInfo); err != nil {
|
|
return err
|
|
}
|
|
rs.SetVersionInfo(v)
|
|
}
|
|
|
|
// replace spaces with underscores as go build behaves weirdly with spaces in syso filename
|
|
targetFile := filepath.Join(options.ProjectData.Path, strings.ReplaceAll(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,
|
|
"arm64": winres.ArchARM64,
|
|
"386": winres.ArchI386,
|
|
}
|
|
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
|
|
}
|