5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 08:29:40 +08:00
wails/v2/pkg/commands/build/packager.go
Andrey Pshenkin b8dae7a6e2
Fix icon issues with windows when project name contains spaces (#3400)
* 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>
2024-04-29 16:59:04 -05:00

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
}