Fix DMG import

This commit is contained in:
Lea Anthony 2025-06-15 23:55:39 +10:00
commit 3e9f7fce4e
2 changed files with 186 additions and 32 deletions

View file

@ -0,0 +1,157 @@
package dmg
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
// Creator handles DMG creation
type Creator struct {
sourcePath string
outputPath string
appName string
backgroundImage string
iconPositions map[string]Position
}
// Position represents icon coordinates in the DMG
type Position struct {
X, Y int
}
// New creates a new DMG creator
func New(sourcePath, outputPath, appName string) (*Creator, error) {
if runtime.GOOS != "darwin" {
return nil, fmt.Errorf("DMG creation is only supported on macOS")
}
// Check if source exists
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
return nil, fmt.Errorf("source path does not exist: %s", sourcePath)
}
return &Creator{
sourcePath: sourcePath,
outputPath: outputPath,
appName: appName,
iconPositions: make(map[string]Position),
}, nil
}
// SetBackgroundImage sets the background image for the DMG
func (c *Creator) SetBackgroundImage(imagePath string) error {
if _, err := os.Stat(imagePath); os.IsNotExist(err) {
return fmt.Errorf("background image does not exist: %s", imagePath)
}
c.backgroundImage = imagePath
return nil
}
// AddIconPosition adds an icon position for the DMG layout
func (c *Creator) AddIconPosition(filename string, x, y int) {
c.iconPositions[filename] = Position{X: x, Y: y}
}
// Create creates the DMG file
func (c *Creator) Create() error {
// Remove existing DMG if it exists
if _, err := os.Stat(c.outputPath); err == nil {
if err := os.Remove(c.outputPath); err != nil {
return fmt.Errorf("failed to remove existing DMG: %w", err)
}
}
// Create a temporary directory for DMG content
tempDir, err := os.MkdirTemp("", "dmg-*")
if err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
// Copy the app bundle to temp directory
appName := filepath.Base(c.sourcePath)
tempAppPath := filepath.Join(tempDir, appName)
if err := c.copyDir(c.sourcePath, tempAppPath); err != nil {
return fmt.Errorf("failed to copy app bundle: %w", err)
}
// Create Applications symlink
applicationsLink := filepath.Join(tempDir, "Applications")
if err := os.Symlink("/Applications", applicationsLink); err != nil {
return fmt.Errorf("failed to create Applications symlink: %w", err)
}
// Copy background image if provided
if c.backgroundImage != "" {
bgName := filepath.Base(c.backgroundImage)
bgPath := filepath.Join(tempDir, bgName)
if err := c.copyFile(c.backgroundImage, bgPath); err != nil {
return fmt.Errorf("failed to copy background image: %w", err)
}
}
// Create DMG using hdiutil
if err := c.createDMGWithHdiutil(tempDir); err != nil {
return fmt.Errorf("failed to create DMG with hdiutil: %w", err)
}
return nil
}
// copyDir recursively copies a directory
func (c *Creator) copyDir(src, dst string) error {
cmd := exec.Command("cp", "-R", src, dst)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to copy directory: %w", err)
}
return nil
}
// copyFile copies a file
func (c *Creator) copyFile(src, dst string) error {
cmd := exec.Command("cp", src, dst)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to copy file: %w", err)
}
return nil
}
// createDMGWithHdiutil creates the DMG using macOS hdiutil
func (c *Creator) createDMGWithHdiutil(sourceDir string) error {
// Calculate size needed for DMG (roughly 2x the source size for safety)
sizeCmd := exec.Command("du", "-sk", sourceDir)
output, err := sizeCmd.Output()
if err != nil {
return fmt.Errorf("failed to calculate directory size: %w", err)
}
// Parse size and add padding
sizeStr := strings.Fields(string(output))[0]
// Create DMG with hdiutil
args := []string{
"create",
"-srcfolder", sourceDir,
"-format", "UDZO",
"-volname", c.appName,
c.outputPath,
}
// Add size if we could determine it
if sizeStr != "" {
// Add 50% padding to the calculated size
args = append([]string{"create", "-size", sizeStr + "k"}, args[1:]...)
args[0] = "create"
}
cmd := exec.Command("hdiutil", args...)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("hdiutil failed: %w\nOutput: %s", err, string(output))
}
return nil
}

View file

@ -8,12 +8,9 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/leaanthony/gosod"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/s"
)
//go:embed build_assets/windows/msix/*
@ -23,13 +20,13 @@ var msixAssets embed.FS
type MSIXOptions struct {
// Info from project config
Info struct {
CompanyName string `json:"companyName"`
ProductName string `json:"productName"`
ProductVersion string `json:"version"`
CompanyName string `json:"companyName"`
ProductName string `json:"productName"`
ProductVersion string `json:"version"`
ProductIdentifier string `json:"productIdentifier"`
Description string `json:"description"`
Copyright string `json:"copyright"`
Comments string `json:"comments"`
Description string `json:"description"`
Copyright string `json:"copyright"`
Comments string `json:"comments"`
}
// File associations
FileAssociations []struct {
@ -41,15 +38,15 @@ type MSIXOptions struct {
MimeType string `json:"mimeType,omitempty"`
} `json:"fileAssociations"`
// MSIX specific options
Publisher string `json:"publisher"`
CertificatePath string `json:"certificatePath"`
CertificatePassword string `json:"certificatePassword,omitempty"`
Publisher string `json:"publisher"`
CertificatePath string `json:"certificatePath"`
CertificatePassword string `json:"certificatePassword,omitempty"`
ProcessorArchitecture string `json:"processorArchitecture"`
ExecutableName string `json:"executableName"`
ExecutablePath string `json:"executablePath"`
OutputPath string `json:"outputPath"`
UseMsixPackagingTool bool `json:"useMsixPackagingTool"`
UseMakeAppx bool `json:"useMakeAppx"`
ExecutableName string `json:"executableName"`
ExecutablePath string `json:"executablePath"`
OutputPath string `json:"outputPath"`
UseMsixPackagingTool bool `json:"useMsixPackagingTool"`
UseMakeAppx bool `json:"useMakeAppx"`
}
// ToolMSIX creates an MSIX package for Windows applications
@ -79,7 +76,7 @@ func ToolMSIX(options *flags.ToolMSIX) error {
// Parse the config
var config struct {
Info map[string]interface{} `json:"info"`
Info map[string]interface{} `json:"info"`
FileAssociations []map[string]interface{} `json:"fileAssociations"`
}
if err := json.Unmarshal(configData, &config); err != nil {
@ -88,15 +85,15 @@ func ToolMSIX(options *flags.ToolMSIX) error {
// Create MSIX options
msixOptions := MSIXOptions{
Publisher: options.Publisher,
CertificatePath: options.CertificatePath,
CertificatePassword: options.CertificatePassword,
Publisher: options.Publisher,
CertificatePath: options.CertificatePath,
CertificatePassword: options.CertificatePassword,
ProcessorArchitecture: options.Arch,
ExecutableName: options.ExecutableName,
ExecutablePath: options.ExecutablePath,
OutputPath: options.OutputPath,
UseMsixPackagingTool: options.UseMsixPackagingTool,
UseMakeAppx: options.UseMakeAppx,
ExecutableName: options.ExecutableName,
ExecutablePath: options.ExecutablePath,
OutputPath: options.OutputPath,
UseMsixPackagingTool: options.UseMsixPackagingTool,
UseMakeAppx: options.UseMakeAppx,
}
// Copy info from config
@ -239,7 +236,7 @@ func createMSIXWithPackagingTool(options *MSIXOptions) error {
// Create the MSIX package
fmt.Println("Creating MSIX package using Microsoft MSIX Packaging Tool...")
args := []string{"create-package", "--template", templatePath}
// Add certificate password if provided
if options.CertificatePassword != "" {
args = append(args, "--certPassword", options.CertificatePassword)
@ -283,14 +280,14 @@ func createMSIXWithMakeAppx(options *MSIXOptions) error {
if options.CertificatePath != "" {
fmt.Println("Signing MSIX package...")
signArgs := []string{"sign", "/fd", "SHA256", "/a", "/f", options.CertificatePath}
// Add certificate password if provided
if options.CertificatePassword != "" {
signArgs = append(signArgs, "/p", options.CertificatePassword)
}
signArgs = append(signArgs, options.OutputPath)
cmd = exec.Command("signtool.exe", signArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -414,7 +411,7 @@ func generateAppxManifest(options *MSIXOptions, outputPath string) error {
func generatePlaceholderImage(outputPath string) error {
// For now, we'll create a simple 1x1 transparent PNG
// In a real implementation, we would generate proper icons based on the application icon
// Create a minimal valid PNG file (1x1 transparent pixel)
pngData := []byte{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
@ -469,7 +466,7 @@ func InstallMSIXTools() error {
if !sdkInstalled {
fmt.Println("Windows SDK is not installed. Please download and install from:")
fmt.Println("https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/")
// Open the download page
cmd = exec.Command("powershell", "-Command", "Start-Process https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/")
if err := cmd.Run(); err != nil {