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 { 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.AssetsDir) if err != nil { return err } return nil } func processPList(options *Options, contentsDirectory string) error { // Check if plist already exists in project dir plistFile := filepath.Join(options.ProjectData.AssetsDir, "mac", "info.plist") // 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") } _, err = tmpl.Parse(string(infoPlist)) if err != nil { return err } // Write the template to a buffer var tpl bytes.Buffer err = tmpl.Execute(&tpl, plistData) if err != nil { return err } // Create the directory if it doesn't exist err = fs.MkDirs(filepath.Dir(targetPlistFile)) 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 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 defaultIcon := fs.RelativePath("./internal/packager/icon1024.png") err = fs.CopyFile(defaultIcon, 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) }