mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
Add MSIX packaging support for Windows applications
This commit is contained in:
parent
16ea4c72fe
commit
4cc8d1cc2f
14 changed files with 1297 additions and 8 deletions
59
docs/src/components/Mermaid.astro
Normal file
59
docs/src/components/Mermaid.astro
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
export interface Props {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const { title = "" } = Astro.props;
|
||||
---
|
||||
|
||||
<script>
|
||||
import mermaid from "mermaid";
|
||||
|
||||
// Postpone mermaid initialization
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
|
||||
function extractMermaidCode() {
|
||||
// Find all mermaid components
|
||||
const mermaidElements = document.querySelectorAll("figure.expandable-diagram");
|
||||
|
||||
mermaidElements.forEach((element) => {
|
||||
// Find the code content in the details section
|
||||
const codeElement = element.querySelector("details pre code");
|
||||
|
||||
if (!codeElement) return;
|
||||
|
||||
// Extract the text content
|
||||
let code = codeElement.textContent || "";
|
||||
|
||||
// Clean up the code
|
||||
code = code.trim();
|
||||
|
||||
// Construct the `pre` element for the diagram code
|
||||
const preElement = document.createElement("pre");
|
||||
preElement.className = "mermaid not-prose";
|
||||
preElement.innerHTML = code;
|
||||
|
||||
// Find the diagram content container and override its content
|
||||
const diagramContainer = element.querySelector(".diagram-content");
|
||||
if (diagramContainer) {
|
||||
diagramContainer.innerHTML = "";
|
||||
diagramContainer.appendChild(preElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the DOM to be fully loaded
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
extractMermaidCode();
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
});
|
||||
</script>
|
||||
|
||||
<figure class="expandable-diagram">
|
||||
<figcaption>{title}</figcaption>
|
||||
<div class="diagram-content">Loading diagram...</div>
|
||||
<details>
|
||||
<summary>Source</summary>
|
||||
<pre><code><slot /></code></pre>
|
||||
</details>
|
||||
</figure>
|
||||
165
docs/src/content/docs/contributing/architecture.mdx
Normal file
165
docs/src/content/docs/contributing/architecture.mdx
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
---
|
||||
title: Wails v3 Architecture
|
||||
description: Deep-dive diagrams and explanations of every moving part inside Wails v3
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import Mermaid from "../../components/Mermaid.astro";
|
||||
|
||||
Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
|
||||
a JavaScript bridge, a task-driven tool-chain and a collection of templates that
|
||||
let you ship native applications powered by modern web tech.
|
||||
|
||||
This page presents the *big picture* in four diagrams:
|
||||
|
||||
1. **Overall Architecture** – how every subsystem connects
|
||||
2. **Runtime Flow** – what happens when JS calls Go and vice-versa
|
||||
3. **Development vs Production** – two modes of the asset server
|
||||
4. **Platform Implementations** – where OS-specific code lives
|
||||
|
||||
---
|
||||
|
||||
## 1 · Overall Architecture
|
||||
|
||||
<Mermaid title="Wails v3 – High-Level Stack">
|
||||
flowchart TD
|
||||
subgraph Developer
|
||||
CLI[wails3 CLI]
|
||||
end
|
||||
subgraph Build["Build-time Tool-chain"]
|
||||
GEN["Binding Generator\n(static analysis)"]
|
||||
TMP["Template Engine"]
|
||||
ASSETDEV["Asset Server (dev)"]
|
||||
PKG["Cross-compilation & Packaging"]
|
||||
end
|
||||
subgraph Runtime["Native Runtime"]
|
||||
RT["Desktop Runtime\n(window, dialogs, tray, …)"]
|
||||
BRIDGE["Message Bridge\n(JSON channel)"]
|
||||
end
|
||||
subgraph App["Your Application"]
|
||||
BACKEND["Go Backend"]
|
||||
FRONTEND["Web Frontend\n(React/Vue/…)"]
|
||||
end
|
||||
|
||||
CLI -->|init| TMP
|
||||
CLI -->|generate| GEN
|
||||
CLI -->|dev| ASSETDEV
|
||||
CLI -->|build| PKG
|
||||
|
||||
GEN -->|Go & TS stubs| BACKEND
|
||||
GEN -->|bindings.json| FRONTEND
|
||||
|
||||
ASSETDEV <-->|HTTP| FRONTEND
|
||||
|
||||
BACKEND <--> BRIDGE <--> FRONTEND
|
||||
BRIDGE <--> RT
|
||||
RT <-->|serve assets| ASSETDEV
|
||||
</Mermaid>
|
||||
|
||||
---
|
||||
|
||||
## 2 · Runtime Call Flow
|
||||
|
||||
<Mermaid title="Runtime – JavaScript ⇄ Go Calling Path">
|
||||
sequenceDiagram
|
||||
participant JS as JavaScript (frontend)
|
||||
participant Bridge as Bridge (WebView callback)
|
||||
participant MP as Message Processor (Go)
|
||||
participant Go as Bound Go Function
|
||||
|
||||
JS->>Bridge: invoke("Greet","Alice")
|
||||
Bridge->>MP: JSON {t:c,id:42,...}
|
||||
MP->>Go: call Greet("Alice")
|
||||
Go-->>MP: "Hello Alice"
|
||||
MP-->>Bridge: JSON {t:r,id:42,result:"Hello Alice"}
|
||||
Bridge-->>JS: Promise.resolve("Hello Alice")
|
||||
</Mermaid>
|
||||
|
||||
Key points:
|
||||
|
||||
* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel
|
||||
* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go
|
||||
* **Promises** – errors propagate as rejections with stack & code
|
||||
|
||||
---
|
||||
|
||||
## 3 · Development vs Production Asset Flow
|
||||
|
||||
<Mermaid title="Dev ↔ Prod Asset Server">
|
||||
flowchart LR
|
||||
subgraph Dev["`wails3 dev`"]
|
||||
VITE["Framework Dev Server\n(port 5173)"]
|
||||
ASDEV["Asset Server (dev)\n(proxy + disk)"]
|
||||
FRONTENDDEV[Browser]
|
||||
end
|
||||
subgraph Prod["`wails3 build`"]
|
||||
EMBED["Embedded FS\n(go:embed)"]
|
||||
ASPROD["Asset Server (prod)\n(read-only)"]
|
||||
FRONTENDPROD[WebView Window]
|
||||
end
|
||||
|
||||
VITE <-->|proxy / HMR| ASDEV
|
||||
ASDEV <-->|http| FRONTENDDEV
|
||||
|
||||
EMBED --> ASPROD
|
||||
ASPROD <-->|in-memory| FRONTENDPROD
|
||||
</Mermaid>
|
||||
|
||||
* In **dev** the server proxies unknown paths to the framework’s live-reload
|
||||
server and serves static assets from disk.
|
||||
* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
|
||||
binary.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Platform-Specific Runtime Split
|
||||
|
||||
<Mermaid title="Per-OS Runtime Files">
|
||||
classDiagram
|
||||
class runtime::Window {
|
||||
+Show()
|
||||
+Hide()
|
||||
+Center()
|
||||
}
|
||||
|
||||
runtime::Window <|-- Window_darwin
|
||||
runtime::Window <|-- Window_linux
|
||||
runtime::Window <|-- Window_windows
|
||||
|
||||
class Window_darwin {
|
||||
//go:build darwin
|
||||
+NSWindow* ptr
|
||||
}
|
||||
class Window_linux {
|
||||
//go:build linux
|
||||
+GtkWindow* ptr
|
||||
}
|
||||
class Window_windows {
|
||||
//go:build windows
|
||||
+HWND ptr
|
||||
}
|
||||
|
||||
note for runtime::Window "Shared interface\nin pkg/application"
|
||||
note for Window_darwin "Objective-C (Cgo)"
|
||||
note for Window_linux "Pure Go GTK calls"
|
||||
note for Window_windows "Win32 API via syscall"
|
||||
</Mermaid>
|
||||
|
||||
Every feature follows this pattern:
|
||||
|
||||
1. **Common interface** in `pkg/application`
|
||||
2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
|
||||
3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
|
||||
|
||||
Missing functionality on an OS should return `ErrCapability` and register
|
||||
availability via `internal/capabilities`.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
These diagrams outline **where the code lives**, **how data moves**, and
|
||||
**which layers own which responsibilities**.
|
||||
Keep them handy while exploring the detailed pages that follow – they are your
|
||||
map to the Wails v3 source tree.
|
||||
|
|
@ -6,6 +6,7 @@ sidebar:
|
|||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
import Mermaid from "../../components/Mermaid.astro";
|
||||
|
||||
## Welcome to the Wails v3 Technical Documentation
|
||||
|
||||
|
|
@ -47,6 +48,59 @@ context you need.
|
|||
|
||||
---
|
||||
|
||||
## Architectural Overview
|
||||
|
||||
<Mermaid title="Wails v3 – End-to-End Flow">
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Developer Environment
|
||||
CLI[wails3 CLI<br/>Init · Dev · Build · Package]
|
||||
end
|
||||
|
||||
subgraph Build-Time
|
||||
GEN[Binding System<br/>(Static Analysis & Codegen)]
|
||||
ASSET[Asset Server<br/>(Dev Proxy · Embed FS)]
|
||||
PKG[Build & Packaging<br/>Pipeline]
|
||||
end
|
||||
|
||||
subgraph Runtime
|
||||
RUNTIME[Desktop Runtime<br/>(Window · Events · Dialogs)]
|
||||
BIND[Bridge<br/>(Message Processor)]
|
||||
end
|
||||
|
||||
subgraph Application
|
||||
GO[Go Backend<br/>(App Logic)]
|
||||
WEB[Web Frontend<br/>(React/Vue/...)]
|
||||
end
|
||||
|
||||
%% Relationships
|
||||
CLI --> |"generate"| GEN
|
||||
CLI --> |"dev / build"| ASSET
|
||||
CLI --> |"compile & package"| PKG
|
||||
|
||||
GEN --> |"Code Stubs + TS"| GO
|
||||
GEN --> |"Bindings JSON"| WEB
|
||||
|
||||
PKG --> |"Final Binary + Installer"| GO
|
||||
|
||||
GO --> |"Function Calls"| BIND
|
||||
WEB --> |"Invoke / Events"| BIND
|
||||
|
||||
RUNTIME <-->|native messages| BIND
|
||||
RUNTIME --> |"Display Assets"| ASSET
|
||||
WEB <-->|HTTP / In-Memory| ASSET
|
||||
```
|
||||
</Mermaid>
|
||||
|
||||
The diagram shows the **end-to-end flow**:
|
||||
|
||||
1. **CLI** drives generation, dev server, compilation, and packaging.
|
||||
2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
|
||||
3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
|
||||
4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
|
||||
|
||||
---
|
||||
|
||||
## What This Documentation Covers
|
||||
|
||||
| Topic | Why It Matters |
|
||||
|
|
|
|||
133
docs/src/content/docs/guides/msix-packaging.mdx
Normal file
133
docs/src/content/docs/guides/msix-packaging.mdx
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# MSIX Packaging (Windows)
|
||||
|
||||
Wails v3 can generate modern **MSIX** installers for Windows applications, providing a cleaner, safer and Store-ready alternative to traditional **NSIS** or plain `.exe` bundles.
|
||||
|
||||
This guide walks through:
|
||||
|
||||
* Prerequisites & tool installation
|
||||
* Building your app as an MSIX package
|
||||
* Signing the package
|
||||
* Command-line reference
|
||||
* Troubleshooting
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
| Requirement | Notes |
|
||||
|-------------|-------|
|
||||
| **Windows 10 1809+ / Windows 11** | MSIX is only supported on Windows. |
|
||||
| **Windows SDK** (for `MakeAppx.exe` & `signtool.exe`) | Install from the [Windows SDK download page](https://developer.microsoft.com/windows/downloads/windows-sdk/). |
|
||||
| **Microsoft MSIX Packaging Tool** (optional) | Available from the Microsoft Store – provides a GUI & CLI. |
|
||||
| **Code-signing certificate** (recommended) | A `.pfx` file generated by your CA or `New-SelfSignedCertificate`. |
|
||||
|
||||
> **Tip:** Wails ships a Task that opens the download pages for you:
|
||||
|
||||
```bash
|
||||
# installs MakeAppx / signtool (via Windows SDK) and the MSIX Packaging Tool
|
||||
wails3 task windows:install:msix:tools
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Building an MSIX package
|
||||
|
||||
### 2.1 Quick CLI
|
||||
|
||||
```bash
|
||||
# Production build + MSIX
|
||||
wails3 tool msix \
|
||||
--name MyApp \ # executable name
|
||||
--executable build/bin/MyApp.exe \
|
||||
--arch x64 \ # x64, x86 or arm64
|
||||
--out build/bin/MyApp-x64.msix
|
||||
```
|
||||
|
||||
The command will:
|
||||
|
||||
1. Create a temporary layout (`AppxManifest.xml`, `Assets/`).
|
||||
2. Call **MakeAppx.exe** (default) or **MsixPackagingTool.exe** if `--use-msix-tool` is passed.
|
||||
3. Optionally sign the package (see §3).
|
||||
|
||||
### 2.2 Using the generated Taskfile
|
||||
|
||||
When you ran `wails init`, Wails created `build/windows/Taskfile.yml`.
|
||||
Packaging with MSIX is a one-liner:
|
||||
|
||||
```bash
|
||||
# default=nsis, override FORMAT
|
||||
wails3 task windows:package FORMAT=msix
|
||||
```
|
||||
|
||||
Output goes to `build/bin/MyApp-<arch>.msix`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Signing the package
|
||||
|
||||
Windows will refuse unsigned MSIX packages unless you enable developer-mode, so signing is strongly recommended.
|
||||
|
||||
```bash
|
||||
wails3 tool msix \
|
||||
--cert build/cert/CodeSign.pfx \
|
||||
--cert-password "pfx-password" \
|
||||
--publisher "CN=MyCompany" \
|
||||
--out build/bin/MyApp.msix
|
||||
```
|
||||
|
||||
* If you pass `--cert`, Wails automatically runs `signtool sign …`.
|
||||
* `--publisher` sets the `Publisher` field inside `AppxManifest.xml`.
|
||||
It **must** match the subject of your certificate.
|
||||
|
||||
---
|
||||
|
||||
## 4. Command-line reference
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--config` | `wails.json` | Project config with **Info** & `fileAssociations`. |
|
||||
| `--name` | — | Executable name inside the package (no spaces). |
|
||||
| `--executable` | — | Path to the built `.exe`. |
|
||||
| `--arch` | `x64` | `x64`, `x86`, or `arm64`. |
|
||||
| `--out` | `<name>.msix` | Output path / filename. |
|
||||
| `--publisher` | `CN=<CompanyName>` | Publisher string in the manifest. |
|
||||
| `--cert` | ― | Path to `.pfx` certificate for signing. |
|
||||
| `--cert-password` | ― | Password for the `.pfx`. |
|
||||
| `--use-msix-tool` | `false` | Use **MsixPackagingTool.exe** instead of **MakeAppx.exe**. |
|
||||
| `--use-makeappx` | `true` | Force MakeAppx even if the MSIX Tool is installed. |
|
||||
|
||||
---
|
||||
|
||||
## 5. File associations
|
||||
|
||||
Wails automatically injects file associations declared in `wails.json` into the package manifest:
|
||||
|
||||
```json
|
||||
"fileAssociations": [
|
||||
{ "ext": "wails", "name": "Wails Project", "description": "Wails file", "role": "Editor" }
|
||||
]
|
||||
```
|
||||
|
||||
After installation, Windows will offer your app as a handler for these extensions.
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| `MakeAppx.exe not found` | Install the Windows SDK and restart the terminal. |
|
||||
| `signtool.exe not found` | Same as above – both live in the SDK’s *bin* folder. |
|
||||
| *Package cannot be installed because publisher mismatch* | The certificate subject (CN) must match `--publisher`. |
|
||||
| *The certificate is not trusted* | Import the certificate into **Trusted Root Certification Authorities** or use a publicly trusted code-signing cert. |
|
||||
| Need GUI | Install **MSIX Packaging Tool** from the store and run `MsixPackagingTool.exe`. The template generated by Wails is fully compatible. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Next steps
|
||||
|
||||
* [Windows Installer (NSIS) guide](./windows-installer.mdx) – legacy format.
|
||||
* [Cross-platform update mechanism](../updates.mdx) – coming soon.
|
||||
* Join the community on Discord to share feedback!
|
||||
|
||||
Happy packaging!
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
@import './mermaid.css';
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
overflow-y: scroll; /* Show vertical scrollbar */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
docs/src/stylesheets/mermaid.css
Normal file
108
docs/src/stylesheets/mermaid.css
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/* Mermaid diagram styling for Starlight */
|
||||
|
||||
/* Container for the whole diagram component */
|
||||
figure.expandable-diagram {
|
||||
margin: 2rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
box-shadow: var(--sl-shadow-sm);
|
||||
}
|
||||
|
||||
/* Dark mode adjustments */
|
||||
:root[data-theme="dark"] figure.expandable-diagram {
|
||||
background-color: var(--sl-color-gray-1);
|
||||
}
|
||||
|
||||
/* Title for the diagram */
|
||||
figure.expandable-diagram figcaption {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--sl-color-text);
|
||||
}
|
||||
|
||||
/* Container for the actual diagram */
|
||||
.diagram-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
min-height: 100px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* The diagram itself */
|
||||
.mermaid {
|
||||
background-color: var(--sl-color-white);
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .mermaid {
|
||||
background-color: var(--sl-color-black);
|
||||
}
|
||||
|
||||
/* Source code details element */
|
||||
figure.expandable-diagram details {
|
||||
margin-top: 1rem;
|
||||
border-top: 1px solid var(--sl-color-gray-5);
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram details {
|
||||
border-top-color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
/* Source button */
|
||||
figure.expandable-diagram summary {
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-text-accent);
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
figure.expandable-diagram summary:hover {
|
||||
background-color: var(--sl-color-gray-5);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram summary:hover {
|
||||
background-color: var(--sl-color-gray-2);
|
||||
}
|
||||
|
||||
/* Source code */
|
||||
figure.expandable-diagram details pre {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--sl-color-gray-7);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] figure.expandable-diagram details pre {
|
||||
background-color: var(--sl-color-gray-0);
|
||||
}
|
||||
|
||||
/* Mermaid diagram specific adjustments */
|
||||
.mermaid .label {
|
||||
font-family: var(--sl-font);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Fix for diagram text in dark mode */
|
||||
:root[data-theme="dark"] .mermaid text {
|
||||
fill: var(--sl-color-white);
|
||||
}
|
||||
|
||||
/* Ensure diagrams are responsive */
|
||||
@media (max-width: 768px) {
|
||||
.diagram-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.mermaid {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,9 +31,16 @@ tasks:
|
|||
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||
|
||||
package:
|
||||
summary: Packages a production build of the application into a `.exe` bundle
|
||||
summary: Packages a production build of the application
|
||||
cmds:
|
||||
- task: create:nsis:installer
|
||||
- |-
|
||||
if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then
|
||||
task: create:msix:package
|
||||
else
|
||||
task: create:nsis:installer
|
||||
fi
|
||||
vars:
|
||||
FORMAT: '{{.FORMAT | default "nsis"}}'
|
||||
|
||||
generate:syso:
|
||||
summary: Generates Windows `.syso` file
|
||||
|
|
@ -58,6 +65,34 @@ tasks:
|
|||
ARCH: '{{.ARCH | default ARCH}}'
|
||||
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
||||
|
||||
create:msix:package:
|
||||
summary: Creates an MSIX package
|
||||
deps:
|
||||
- task: build
|
||||
vars:
|
||||
PRODUCTION: "true"
|
||||
cmds:
|
||||
- |-
|
||||
wails3 tool msix \
|
||||
--config "{{.ROOT_DIR}}/wails.json" \
|
||||
--name "{{.APP_NAME}}" \
|
||||
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
|
||||
--arch "{{.ARCH}}" \
|
||||
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
|
||||
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
|
||||
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
||||
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
||||
vars:
|
||||
ARCH: '{{.ARCH | default ARCH}}'
|
||||
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
||||
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
||||
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
||||
|
||||
install:msix:tools:
|
||||
summary: Installs tools required for MSIX packaging
|
||||
cmds:
|
||||
- wails3 tool msix-install-tools
|
||||
|
||||
run:
|
||||
cmds:
|
||||
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
|
||||
|
||||
<Identity
|
||||
Name="{{.Info.ProductIdentifier}}"
|
||||
Publisher="{{.Publisher}}"
|
||||
Version="{{.Info.Version}}.0"
|
||||
ProcessorArchitecture="{{.ProcessorArchitecture}}" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>{{.Info.ProductName}}</DisplayName>
|
||||
<PublisherDisplayName>{{.Info.CompanyName}}</PublisherDisplayName>
|
||||
<Description>{{.Info.Description}}</Description>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="{{.Info.ProductIdentifier}}" Executable="{{.ExecutableName}}" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="{{.Info.ProductName}}"
|
||||
Description="{{.Info.Description}}"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="{{.ExecutableName}}" />
|
||||
{{if .FileAssociations}}
|
||||
<uap:Extension Category="windows.fileTypeAssociation">
|
||||
<uap:FileTypeAssociation Name="{{.Info.ProductIdentifier}}">
|
||||
<uap:DisplayName>{{.Info.ProductName}}</uap:DisplayName>
|
||||
<uap:Logo>Assets\FileIcon.png</uap:Logo>
|
||||
<uap:InfoTip>{{.Info.ProductName}} File</uap:InfoTip>
|
||||
<uap:SupportedFileTypes>
|
||||
{{range .FileAssociations}}
|
||||
<uap:FileType>.{{.Ext}}</uap:FileType>
|
||||
{{end}}
|
||||
</uap:SupportedFileTypes>
|
||||
</uap:FileTypeAssociation>
|
||||
</uap:Extension>
|
||||
{{end}}
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
{{if .FileAssociations}}
|
||||
<uap:Capability Name="documentsLibrary" />
|
||||
{{end}}
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MsixPackagingToolTemplate
|
||||
xmlns="http://schemas.microsoft.com/msix/packaging/msixpackagingtool/template/2022">
|
||||
<Settings
|
||||
AllowTelemetry="false"
|
||||
ApplyACLsToPackageFiles="true"
|
||||
GenerateCommandLineFile="true"
|
||||
AllowPromptForPassword="false">
|
||||
</Settings>
|
||||
<Installer
|
||||
Path="{{.ExecutablePath}}"
|
||||
Arguments=""
|
||||
InstallLocation="C:\Program Files\{{.Info.CompanyName}}\{{.Info.ProductName}}">
|
||||
</Installer>
|
||||
<PackageInformation
|
||||
PackageName="{{.Info.ProductName}}"
|
||||
PackageDisplayName="{{.Info.ProductName}}"
|
||||
PublisherName="CN={{.Info.CompanyName}}"
|
||||
PublisherDisplayName="{{.Info.CompanyName}}"
|
||||
Version="{{.Info.Version}}.0"
|
||||
PackageDescription="{{.Info.Description}}">
|
||||
<Capabilities>
|
||||
<Capability Name="runFullTrust" />
|
||||
{{if .FileAssociations}}
|
||||
<Capability Name="documentsLibrary" />
|
||||
{{end}}
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application
|
||||
Id="{{.Info.ProductIdentifier}}"
|
||||
Description="{{.Info.Description}}"
|
||||
DisplayName="{{.Info.ProductName}}"
|
||||
ExecutableName="{{.ExecutableName}}"
|
||||
EntryPoint="Windows.FullTrustApplication">
|
||||
{{if .FileAssociations}}
|
||||
<Extensions>
|
||||
<Extension Category="windows.fileTypeAssociation">
|
||||
<FileTypeAssociation Name="{{.Info.ProductIdentifier}}">
|
||||
{{range .FileAssociations}}
|
||||
<SupportedFileTypes>
|
||||
<FileType>.{{.Ext}}</FileType>
|
||||
</SupportedFileTypes>
|
||||
{{end}}
|
||||
</FileTypeAssociation>
|
||||
</Extension>
|
||||
</Extensions>
|
||||
{{end}}
|
||||
</Application>
|
||||
</Applications>
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
<Properties>
|
||||
<Framework>false</Framework>
|
||||
<DisplayName>{{.Info.ProductName}}</DisplayName>
|
||||
<PublisherDisplayName>{{.Info.CompanyName}}</PublisherDisplayName>
|
||||
<Description>{{.Info.Description}}</Description>
|
||||
<Logo>Assets\AppIcon.png</Logo>
|
||||
</Properties>
|
||||
</PackageInformation>
|
||||
<SaveLocation PackagePath="{{.OutputPath}}" />
|
||||
<PackageIntegrity>
|
||||
<CertificatePath>{{.CertificatePath}}</CertificatePath>
|
||||
</PackageIntegrity>
|
||||
</MsixPackagingToolTemplate>
|
||||
488
v3/internal/commands/msix.go
Normal file
488
v3/internal/commands/msix.go
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"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/*
|
||||
var msixAssets embed.FS
|
||||
|
||||
// MSIXOptions represents the configuration for MSIX packaging
|
||||
type MSIXOptions struct {
|
||||
// Info from project config
|
||||
Info struct {
|
||||
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"`
|
||||
}
|
||||
// File associations
|
||||
FileAssociations []struct {
|
||||
Ext string `json:"ext"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IconName string `json:"iconName"`
|
||||
Role string `json:"role"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
} `json:"fileAssociations"`
|
||||
// MSIX specific options
|
||||
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"`
|
||||
}
|
||||
|
||||
// ToolMSIX creates an MSIX package for Windows applications
|
||||
func ToolMSIX(options *flags.ToolMSIX) error {
|
||||
DisableFooter = true
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
return fmt.Errorf("MSIX packaging is only supported on Windows")
|
||||
}
|
||||
|
||||
// Check if required tools are installed
|
||||
if err := checkMSIXTools(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load project configuration
|
||||
configPath := options.ConfigPath
|
||||
if configPath == "" {
|
||||
configPath = "wails.json"
|
||||
}
|
||||
|
||||
// Read the config file
|
||||
configData, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
|
||||
// Parse the config
|
||||
var config struct {
|
||||
Info map[string]interface{} `json:"info"`
|
||||
FileAssociations []map[string]interface{} `json:"fileAssociations"`
|
||||
}
|
||||
if err := json.Unmarshal(configData, &config); err != nil {
|
||||
return fmt.Errorf("error parsing config file: %w", err)
|
||||
}
|
||||
|
||||
// Create MSIX options
|
||||
msixOptions := MSIXOptions{
|
||||
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,
|
||||
}
|
||||
|
||||
// Copy info from config
|
||||
infoBytes, err := json.Marshal(config.Info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling info: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(infoBytes, &msixOptions.Info); err != nil {
|
||||
return fmt.Errorf("error unmarshaling info: %w", err)
|
||||
}
|
||||
|
||||
// Copy file associations from config
|
||||
if len(config.FileAssociations) > 0 {
|
||||
faBytes, err := json.Marshal(config.FileAssociations)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling file associations: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(faBytes, &msixOptions.FileAssociations); err != nil {
|
||||
return fmt.Errorf("error unmarshaling file associations: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate options
|
||||
if err := validateMSIXOptions(&msixOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create MSIX package
|
||||
if msixOptions.UseMsixPackagingTool {
|
||||
return createMSIXWithPackagingTool(&msixOptions)
|
||||
} else if msixOptions.UseMakeAppx {
|
||||
return createMSIXWithMakeAppx(&msixOptions)
|
||||
}
|
||||
|
||||
// Default to MakeAppx if neither is specified
|
||||
return createMSIXWithMakeAppx(&msixOptions)
|
||||
}
|
||||
|
||||
// checkMSIXTools checks if the required tools for MSIX packaging are installed
|
||||
func checkMSIXTools(options *flags.ToolMSIX) error {
|
||||
// Check if MsixPackagingTool is installed if requested
|
||||
if options.UseMsixPackagingTool {
|
||||
cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Microsoft MSIX Packaging Tool is not installed. Please install it from the Microsoft Store")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if MakeAppx is available if requested
|
||||
if options.UseMakeAppx {
|
||||
cmd := exec.Command("where", "MakeAppx.exe")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK")
|
||||
}
|
||||
}
|
||||
|
||||
// If neither is specified, check for MakeAppx as the default
|
||||
if !options.UseMsixPackagingTool && !options.UseMakeAppx {
|
||||
cmd := exec.Command("where", "MakeAppx.exe")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("MakeAppx.exe is not found in PATH. Please install the Windows SDK")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if signtool is available for signing
|
||||
if options.CertificatePath != "" {
|
||||
cmd := exec.Command("where", "signtool.exe")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("signtool.exe is not found in PATH. Please install the Windows SDK")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMSIXOptions validates the MSIX options
|
||||
func validateMSIXOptions(options *MSIXOptions) error {
|
||||
// Check required fields
|
||||
if options.Info.ProductName == "" {
|
||||
return fmt.Errorf("product name is required")
|
||||
}
|
||||
if options.Info.ProductIdentifier == "" {
|
||||
return fmt.Errorf("product identifier is required")
|
||||
}
|
||||
if options.Info.CompanyName == "" {
|
||||
return fmt.Errorf("company name is required")
|
||||
}
|
||||
if options.ExecutableName == "" {
|
||||
return fmt.Errorf("executable name is required")
|
||||
}
|
||||
if options.ExecutablePath == "" {
|
||||
return fmt.Errorf("executable path is required")
|
||||
}
|
||||
|
||||
// Validate executable path
|
||||
if _, err := os.Stat(options.ExecutablePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("executable file not found: %s", options.ExecutablePath)
|
||||
}
|
||||
|
||||
// Validate certificate path if provided
|
||||
if options.CertificatePath != "" {
|
||||
if _, err := os.Stat(options.CertificatePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("certificate file not found: %s", options.CertificatePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Set default processor architecture if not provided
|
||||
if options.ProcessorArchitecture == "" {
|
||||
options.ProcessorArchitecture = "x64"
|
||||
}
|
||||
|
||||
// Set default publisher if not provided
|
||||
if options.Publisher == "" {
|
||||
options.Publisher = fmt.Sprintf("CN=%s", options.Info.CompanyName)
|
||||
}
|
||||
|
||||
// Set default output path if not provided
|
||||
if options.OutputPath == "" {
|
||||
options.OutputPath = filepath.Join(".", fmt.Sprintf("%s.msix", options.Info.ProductName))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMSIXWithPackagingTool creates an MSIX package using the Microsoft MSIX Packaging Tool
|
||||
func createMSIXWithPackagingTool(options *MSIXOptions) error {
|
||||
// Create a temporary directory for the template
|
||||
tempDir, err := os.MkdirTemp("", "wails-msix-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating temporary directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Generate the template file
|
||||
templatePath := filepath.Join(tempDir, "template.xml")
|
||||
if err := generateMSIXTemplate(options, templatePath); err != nil {
|
||||
return fmt.Errorf("error generating MSIX template: %w", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
cmd := exec.Command("MsixPackagingTool.exe", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error creating MSIX package: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMSIXWithMakeAppx creates an MSIX package using MakeAppx.exe
|
||||
func createMSIXWithMakeAppx(options *MSIXOptions) error {
|
||||
// Create a temporary directory for the package structure
|
||||
tempDir, err := os.MkdirTemp("", "wails-msix-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating temporary directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create the package structure
|
||||
if err := createMSIXPackageStructure(options, tempDir); err != nil {
|
||||
return fmt.Errorf("error creating MSIX package structure: %w", err)
|
||||
}
|
||||
|
||||
// Create the MSIX package
|
||||
fmt.Println("Creating MSIX package using MakeAppx.exe...")
|
||||
cmd := exec.Command("MakeAppx.exe", "pack", "/d", tempDir, "/p", options.OutputPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error creating MSIX package: %w", err)
|
||||
}
|
||||
|
||||
// Sign the package if certificate is provided
|
||||
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
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error signing MSIX package: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("MSIX package created successfully: %s\n", options.OutputPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateMSIXTemplate generates the MSIX template file for the Microsoft MSIX Packaging Tool
|
||||
func generateMSIXTemplate(options *MSIXOptions, outputPath string) error {
|
||||
// Read the template file
|
||||
templateData, err := msixAssets.ReadFile("build_assets/windows/msix/template.xml.tmpl")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading template file: %w", err)
|
||||
}
|
||||
|
||||
// Parse the template
|
||||
tmpl, err := template.New("msix-template").Parse(string(templateData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing template: %w", err)
|
||||
}
|
||||
|
||||
// Create the output file
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Execute the template
|
||||
if err := tmpl.Execute(file, options); err != nil {
|
||||
return fmt.Errorf("error executing template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMSIXPackageStructure creates the MSIX package structure for MakeAppx.exe
|
||||
func createMSIXPackageStructure(options *MSIXOptions, outputDir string) error {
|
||||
// Create the Assets directory
|
||||
assetsDir := filepath.Join(outputDir, "Assets")
|
||||
if err := os.MkdirAll(assetsDir, 0755); err != nil {
|
||||
return fmt.Errorf("error creating Assets directory: %w", err)
|
||||
}
|
||||
|
||||
// Generate the AppxManifest.xml file
|
||||
manifestPath := filepath.Join(outputDir, "AppxManifest.xml")
|
||||
if err := generateAppxManifest(options, manifestPath); err != nil {
|
||||
return fmt.Errorf("error generating AppxManifest.xml: %w", err)
|
||||
}
|
||||
|
||||
// Copy the executable
|
||||
executableDest := filepath.Join(outputDir, filepath.Base(options.ExecutablePath))
|
||||
if err := copyFile(options.ExecutablePath, executableDest); err != nil {
|
||||
return fmt.Errorf("error copying executable: %w", err)
|
||||
}
|
||||
|
||||
// Copy any additional files needed for the application
|
||||
// This would include DLLs, resources, etc.
|
||||
// For now, we'll just copy the executable
|
||||
|
||||
// Generate placeholder assets
|
||||
assets := []string{
|
||||
"Square150x150Logo.png",
|
||||
"Square44x44Logo.png",
|
||||
"Wide310x150Logo.png",
|
||||
"SplashScreen.png",
|
||||
"StoreLogo.png",
|
||||
}
|
||||
|
||||
// Add FileIcon.png if there are file associations
|
||||
if len(options.FileAssociations) > 0 {
|
||||
assets = append(assets, "FileIcon.png")
|
||||
}
|
||||
|
||||
// Generate placeholder assets
|
||||
for _, asset := range assets {
|
||||
assetPath := filepath.Join(assetsDir, asset)
|
||||
if err := generatePlaceholderImage(assetPath); err != nil {
|
||||
return fmt.Errorf("error generating placeholder image %s: %w", asset, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateAppxManifest generates the AppxManifest.xml file
|
||||
func generateAppxManifest(options *MSIXOptions, outputPath string) error {
|
||||
// Read the template file
|
||||
templateData, err := msixAssets.ReadFile("build_assets/windows/msix/app_manifest.xml.tmpl")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading template file: %w", err)
|
||||
}
|
||||
|
||||
// Parse the template
|
||||
tmpl, err := template.New("appx-manifest").Parse(string(templateData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing template: %w", err)
|
||||
}
|
||||
|
||||
// Create the output file
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Execute the template
|
||||
if err := tmpl.Execute(file, options); err != nil {
|
||||
return fmt.Errorf("error executing template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generatePlaceholderImage generates a placeholder image file
|
||||
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,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00,
|
||||
0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
|
||||
0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49,
|
||||
0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
|
||||
}
|
||||
|
||||
return os.WriteFile(outputPath, pngData, 0644)
|
||||
}
|
||||
|
||||
// copyFile copies a file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
// Read the source file
|
||||
data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the destination file
|
||||
return os.WriteFile(dst, data, 0644)
|
||||
}
|
||||
|
||||
// InstallMSIXTools installs the required tools for MSIX packaging
|
||||
func InstallMSIXTools() error {
|
||||
// Check if running on Windows
|
||||
if runtime.GOOS != "windows" {
|
||||
return fmt.Errorf("MSIX packaging is only supported on Windows")
|
||||
}
|
||||
|
||||
fmt.Println("Installing MSIX packaging tools...")
|
||||
|
||||
// Install MSIX Packaging Tool from Microsoft Store
|
||||
fmt.Println("Installing Microsoft MSIX Packaging Tool from Microsoft Store...")
|
||||
cmd := exec.Command("powershell", "-Command", "Start-Process ms-windows-store://pdp/?ProductId=9N5R1TQPJVBP")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error launching Microsoft Store: %w", err)
|
||||
}
|
||||
|
||||
// Check if Windows SDK is installed
|
||||
fmt.Println("Checking for Windows SDK...")
|
||||
sdkInstalled := false
|
||||
cmd = exec.Command("where", "MakeAppx.exe")
|
||||
if err := cmd.Run(); err == nil {
|
||||
sdkInstalled = true
|
||||
fmt.Println("Windows SDK is already installed.")
|
||||
}
|
||||
|
||||
// Install Windows SDK if not installed
|
||||
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 {
|
||||
return fmt.Errorf("error opening Windows SDK download page: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("MSIX packaging tools installation initiated. Please complete the installation process in the opened windows.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// init registers the MSIX command
|
||||
func init() {
|
||||
// Register the MSIX command in the CLI
|
||||
// This will be called by the CLI framework
|
||||
}
|
||||
|
|
@ -4,17 +4,23 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/commands/dmg"
|
||||
"github.com/wailsapp/wails/v3/internal/flags"
|
||||
"github.com/wailsapp/wails/v3/internal/packager"
|
||||
)
|
||||
|
||||
// ToolPackage generates a Linux package in the specified format using nfpm
|
||||
// ToolPackage generates a package in the specified format
|
||||
func ToolPackage(options *flags.ToolPackage) error {
|
||||
DisableFooter = true
|
||||
|
||||
if options.ConfigPath == "" {
|
||||
// Check if we're creating a DMG
|
||||
isDMG := strings.ToLower(options.Format) == "dmg" || options.CreateDMG
|
||||
|
||||
// Config file is required for Linux packages but optional for DMG
|
||||
if options.ConfigPath == "" && !isDMG {
|
||||
return fmt.Errorf("please provide a config file using the -config flag")
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +28,48 @@ func ToolPackage(options *flags.ToolPackage) error {
|
|||
return fmt.Errorf("please provide an executable name using the -name flag")
|
||||
}
|
||||
|
||||
// Validate format
|
||||
// Handle DMG creation for macOS
|
||||
if isDMG {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return fmt.Errorf("DMG creation is only supported on macOS")
|
||||
}
|
||||
|
||||
// For DMG, we expect the .app bundle to already exist
|
||||
appPath := filepath.Join(options.Out, fmt.Sprintf("%s.app", options.ExecutableName))
|
||||
if _, err := os.Stat(appPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("application bundle not found: %s", appPath)
|
||||
}
|
||||
|
||||
// Create output path for DMG
|
||||
dmgPath := filepath.Join(options.Out, fmt.Sprintf("%s.dmg", options.ExecutableName))
|
||||
|
||||
// Create DMG creator
|
||||
dmgCreator, err := dmg.New(appPath, dmgPath, options.ExecutableName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating DMG: %w", err)
|
||||
}
|
||||
|
||||
// Set background image if provided
|
||||
if options.BackgroundImage != "" {
|
||||
if err := dmgCreator.SetBackgroundImage(options.BackgroundImage); err != nil {
|
||||
return fmt.Errorf("error setting background image: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set default icon positions
|
||||
dmgCreator.AddIconPosition(filepath.Base(appPath), 150, 175)
|
||||
dmgCreator.AddIconPosition("Applications", 450, 175)
|
||||
|
||||
// Create the DMG
|
||||
if err := dmgCreator.Create(); err != nil {
|
||||
return fmt.Errorf("error creating DMG: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("DMG created successfully: %s\n", dmgPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// For Linux packages, continue with existing logic
|
||||
var pkgType packager.PackageType
|
||||
switch strings.ToLower(options.Format) {
|
||||
case "deb":
|
||||
|
|
@ -32,7 +79,7 @@ func ToolPackage(options *flags.ToolPackage) error {
|
|||
case "archlinux":
|
||||
pkgType = packager.ARCH
|
||||
default:
|
||||
return fmt.Errorf("unsupported package format '%s'. Supported formats: deb, rpm, archlinux", options.Format)
|
||||
return fmt.Errorf("unsupported package format '%s'. Supported formats: deb, rpm, archlinux, dmg", options.Format)
|
||||
}
|
||||
|
||||
// Get absolute path of config file
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/samber/lo"
|
||||
"github.com/wailsapp/go-webview2/webviewloader"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getInfo() (map[string]string, bool) {
|
||||
|
|
@ -30,8 +31,42 @@ func getNSISVersion() string {
|
|||
return string(output)
|
||||
}
|
||||
|
||||
func getMakeAppxVersion() string {
|
||||
// Check if MakeAppx.exe is available (part of Windows SDK)
|
||||
_, err := exec.LookPath("MakeAppx.exe")
|
||||
if err != nil {
|
||||
return "Not Installed"
|
||||
}
|
||||
return "Installed"
|
||||
}
|
||||
|
||||
func getMSIXPackagingToolVersion() string {
|
||||
// Check if MSIX Packaging Tool is installed
|
||||
// Use PowerShell to check if the app is installed from Microsoft Store
|
||||
cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool")
|
||||
output, err := cmd.Output()
|
||||
if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MsixPackagingTool") {
|
||||
return "Not Installed"
|
||||
}
|
||||
return "Installed"
|
||||
}
|
||||
|
||||
func getSignToolVersion() string {
|
||||
// Check if signtool.exe is available (part of Windows SDK)
|
||||
_, err := exec.LookPath("signtool.exe")
|
||||
if err != nil {
|
||||
return "Not Installed"
|
||||
}
|
||||
return "Installed"
|
||||
}
|
||||
|
||||
func checkPlatformDependencies(result map[string]string, ok *bool) {
|
||||
checkCommonDependencies(result, ok)
|
||||
// add nsis
|
||||
result["NSIS"] = getNSISVersion()
|
||||
|
||||
// Add MSIX tooling checks
|
||||
result["MakeAppx.exe (Windows SDK)"] = getMakeAppxVersion()
|
||||
result["MSIX Packaging Tool"] = getMSIXPackagingToolVersion()
|
||||
result["SignTool.exe (Windows SDK)"] = getSignToolVersion()
|
||||
}
|
||||
|
|
|
|||
26
v3/internal/flags/msix.go
Normal file
26
v3/internal/flags/msix.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package flags
|
||||
|
||||
// ToolMSIX represents the options for the MSIX packaging command
|
||||
type ToolMSIX struct {
|
||||
Common
|
||||
|
||||
// Project configuration
|
||||
ConfigPath string `name:"config" description:"Path to the project configuration file" default:"wails.json"`
|
||||
|
||||
// MSIX package information
|
||||
Publisher string `name:"publisher" description:"Publisher name for the MSIX package (e.g., CN=CompanyName)" default:""`
|
||||
|
||||
// Certificate for signing
|
||||
CertificatePath string `name:"cert" description:"Path to the certificate file for signing the MSIX package" default:""`
|
||||
CertificatePassword string `name:"cert-password" description:"Password for the certificate file" default:""`
|
||||
|
||||
// Build options
|
||||
Arch string `name:"arch" description:"Architecture of the package (x64, x86, arm64)" default:"x64"`
|
||||
ExecutableName string `name:"name" description:"Name of the executable in the package" default:""`
|
||||
ExecutablePath string `name:"executable" description:"Path to the executable file to package" default:""`
|
||||
OutputPath string `name:"out" description:"Path where the MSIX package will be saved" default:""`
|
||||
|
||||
// Tool selection
|
||||
UseMsixPackagingTool bool `name:"use-msix-tool" description:"Use the Microsoft MSIX Packaging Tool for packaging" default:"false"`
|
||||
UseMakeAppx bool `name:"use-makeappx" description:"Use MakeAppx.exe for packaging" default:"true"`
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ package flags
|
|||
type ToolPackage struct {
|
||||
Common
|
||||
|
||||
Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux)" default:"deb"`
|
||||
Format string `name:"format" description:"Package format to generate (deb, rpm, archlinux, dmg)" default:"deb"`
|
||||
ExecutableName string `name:"name" description:"Name of the executable to package" default:"myapp"`
|
||||
ConfigPath string `name:"config" description:"Path to the package configuration file" default:""`
|
||||
Out string `name:"out" description:"Path to the output dir" default:"."`
|
||||
BackgroundImage string `name:"background" description:"Path to an optional background image for the DMG" default:""`
|
||||
CreateDMG bool `name:"create-dmg" description:"Create a DMG file (macOS only)" default:"false"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue