Make the Windows embedded WebView layer real (#86)
* Make the Windows embedded WebView layer real - Vendor the WebView2 SDK header and loader under third_party/webview2 (BSD-3-Clause, license preserved) so repo state alone compiles the embedded layer; a missing header is now a hard error instead of a silent stub - Fix the conformance blockers behind the guard: a local WRL callback factory for mingw, the uncaptured bridge-handler variable, an EventToken shim, and STA COM initialization on the host thread - Stage the arch-matched loader beside built, run, packaged, and scaffolded apps, mirror it into the npm payload, and pin the wiring with vendor and loader-layout tests * Carry the SDK root through package shortcuts and generated builds - packageShortcut and package-ios now pass the environ map into createPackage like the package verb, so NATIVE_SDK_PATH resolves the framework root for loader staging from standalone binaries - Generated frontend builds export NATIVE_SDK_PATH on the package command and stage the loader dir on the dev command's PATH, mirroring the SDK-dependency graph
This commit is contained in:
@@ -90,6 +90,12 @@ jobs:
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.16.0
|
||||
# Builds the WebView example with the system engine, compiling the
|
||||
# full embedded-WebView host against the vendored WebView2 SDK
|
||||
# header (third_party/webview2). The host refuses to fall back to
|
||||
# the stubbed layer, so a regression — a missing header, a broken
|
||||
# include path, or a conformance error in the embedded layer — is a
|
||||
# compile failure here, not a silent WebViewNotFound at runtime.
|
||||
- run: zig build test-webview-system-link -Dplatform=windows
|
||||
|
||||
cef-platform-tooling:
|
||||
|
||||
@@ -17,6 +17,7 @@ packages/native-sdk/build/
|
||||
packages/native-sdk/build.zig
|
||||
packages/native-sdk/build.zig.zon
|
||||
packages/native-sdk/app.zon
|
||||
packages/native-sdk/third_party/
|
||||
packages/native-sdk/LICENSE
|
||||
# npm pack output
|
||||
packages/native-sdk/*.tgz
|
||||
|
||||
@@ -482,6 +482,26 @@ pub fn build(b: *std.Build) void {
|
||||
.{ .path = "src/platform/linux/gtk_host.c", .pattern = "change != NATIVE_SDK_GST_STATE_CHANGE_ASYNC" },
|
||||
.{ .path = "src/platform/linux/gtk_host.c", .pattern = "#define NATIVE_SDK_GST_STATE_CHANGE_ASYNC 2" },
|
||||
});
|
||||
// The embedded-WebView layer must stay real: the vendored WebView2
|
||||
// SDK header turns the guard on, every first-party build graph puts
|
||||
// it on the include path, and a build that cannot see it fails at
|
||||
// compile time instead of quietly shipping the stubbed host (whose
|
||||
// WebView loads report WebViewNotFound at runtime).
|
||||
addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-webview2-vendor", "Verify the vendored WebView2 SDK stays wired into every Windows build graph", &.{
|
||||
.{ .path = "third_party/webview2/include/WebView2.h", .pattern = "CreateCoreWebView2EnvironmentWithOptions" },
|
||||
.{ .path = "third_party/webview2/include/EventToken.h", .pattern = "EventRegistrationToken" },
|
||||
.{ .path = "third_party/webview2/LICENSE.txt", .pattern = "Redistribution and use in source and binary forms" },
|
||||
.{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "#error \"WebView2.h not found" },
|
||||
.{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "LoadLibraryW(L\"WebView2Loader.dll\")" },
|
||||
.{ .path = "build/app.zig", .pattern = "app_mod.addIncludePath(dep.path(\"third_party/webview2/include\"));" },
|
||||
.{ .path = "build/app.zig", .pattern = "third_party/webview2/x64/WebView2Loader.dll" },
|
||||
.{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/include" },
|
||||
.{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/x64/WebView2Loader.dll" },
|
||||
});
|
||||
addLayoutCheckStep(b, test_step, "test-windows-webview2-loader-layout", "Verify the vendored WebView2 loader binaries are present", &.{
|
||||
"third_party/webview2/x64/WebView2Loader.dll",
|
||||
"third_party/webview2/arm64/WebView2Loader.dll",
|
||||
});
|
||||
addFileContainsCheckStep(b, file_contains_checker, test_step, "test-windows-packaged-assets-webview2", "Verify Windows packaged assets are served through WebView2 request interception", &.{
|
||||
.{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "constexpr const char *kAssetVirtualOrigin = \"https://native-sdk-app.localhost\";" },
|
||||
.{ .path = "src/platform/windows/webview2_host.cpp", .pattern = "return virtualAssetEntryUrl(webview.asset_entry);" },
|
||||
|
||||
+37
-1
@@ -251,6 +251,7 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(dep, target, run, web_engine);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -315,6 +316,10 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
|
||||
};
|
||||
if (package_target) |package_target_name| {
|
||||
const package_run = b.addRunArtifact(dep.artifact("native"));
|
||||
// The CLI resolves SDK-owned package inputs (the vendored
|
||||
// WebView2 loader) from the framework root; the cached artifact's
|
||||
// own location cannot derive it, so hand it over explicitly.
|
||||
package_run.setEnvironmentVariable("NATIVE_SDK_PATH", dep.builder.pathFromRoot("."));
|
||||
package_run.addArgs(&.{ "package", "--target", package_target_name, "--manifest", "app.zon", "--output" });
|
||||
package_run.addArg(if (host_os == .macos)
|
||||
b.fmt("zig-out/package/{s}.app", .{app_options.name})
|
||||
@@ -549,7 +554,19 @@ fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Res
|
||||
// bitmap-stretching a 96-DPI surface on scaled displays.
|
||||
exe.win32_manifest = dep.path("assets/native-sdk.manifest");
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(dep.path("third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(dep.path(webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -582,6 +599,25 @@ fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Res
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(dep: *std.Build.Dependency, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(dep.builder.pathFromRoot(loader_dir));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fix: **Windows embedded WebView is real from a plain checkout**: the WebView2 SDK header and loader are vendored under `third_party/webview2/` (BSD-licensed), every build graph puts the header on the include path, and the host now refuses to compile with the WebView layer silently stubbed — previously every Windows build shipped the stub and WebView loads reported `WebViewNotFound` at runtime.
|
||||
- **WebView2 host conformance fixes**: a missing lambda capture in the bridge message handler, an event-handler factory that works with the mingw WRL subset zig ships, and COM apartment initialization on the host thread so environment creation no longer fails with `CO_E_NOTINITIALIZED`.
|
||||
- **WebView2Loader.dll ships with the app**: `zig build` installs the architecture's loader next to the executable, `zig build run` resolves it during dev runs, and `native package --target windows` includes it in the artifact (the Evergreen WebView2 runtime itself is preinstalled on current Windows).
|
||||
@@ -65,7 +65,7 @@ macOS, Linux, and Windows run full desktop apps through their own platform hosts
|
||||
<tr>
|
||||
<td>Web content (WebViews)</td>
|
||||
<td><Tier tier="full" note="system WebView by default; optional bundled Chromium via CEF" fn={6} /></td>
|
||||
<td><Tier tier="caveats" note="system WebView host still maturing; native-rendered apps carry no web engine" fn={6} /></td>
|
||||
<td><Tier tier="caveats" note="system WebView (WebView2); needs the WebView2 runtime, preinstalled on current Windows" fn={6} /></td>
|
||||
<td><Tier tier="full" note="system WebView" fn={6} /></td>
|
||||
<td><Tier tier="embed" note="system WebView workspace inside your host app" fn={6} /></td>
|
||||
<td><Tier tier="embed" note="system WebView workspace inside your host app" fn={6} /></td>
|
||||
@@ -102,7 +102,7 @@ macOS, Linux, and Windows run full desktop apps through their own platform hosts
|
||||
3. Windows maps native IME composition onto the shared IME events, with real-hardware IME verification still pending. The iOS toolkit host forwards touch, the system keyboard, and IME composition, verified with real injected input on the simulator; the Android toolkit host forwards touch, shows and hides the soft keyboard from the runtime's focus state, and routes committed text and IME composition through the same embed IME events, verified with injected input on the emulator.
|
||||
4. App menus are native on all three desktops. Hosts without a native context-menu presenter — Linux and Windows today — present the same declared context menu as an anchored canvas surface; authors declare one menu either way.
|
||||
5. Tray support is implemented on macOS (`NSStatusItem`) and Windows; Linux tray calls return `UnsupportedService` until a portable status-notifier implementation is selected.
|
||||
6. Web engines only apply to apps that embed web content; native-rendered apps carry none. The system WebView is the default engine on every desktop platform; bundled Chromium through CEF is available on macOS only, and Linux/Windows Chromium builds fail early instead of silently substituting an engine. See [Web Engines](/web-engines). The mobile shell examples embed the platform WebView as the content workspace.
|
||||
6. Web engines only apply to apps that embed web content; native-rendered apps carry none. The system WebView is the default engine on every desktop platform; bundled Chromium through CEF is available on macOS only, and Linux/Windows Chromium builds fail early instead of silently substituting an engine. The Windows system engine is WebView2: its Evergreen runtime ships with Windows 11 and current Windows 10 (older machines need the runtime installer; `native doctor` checks for it), and the build stages the vendored loader next to the executable automatically. See [Web Engines](/web-engines). The mobile shell examples embed the platform WebView as the content workspace.
|
||||
7. `native package` targets all five platforms: macOS gets a `.app` bundle (plus `zig build dmg`), Linux an install tree, Windows a distributable directory with a per-user file-type registration script, iOS a complete generated Xcode project — toolkit host sources, Info.plist, asset catalog, shared scheme, and the device-slice embed library — that `xcodebuild archive` builds with zero edits (code signing stays a manual step, like notarization), and Android a complete generated host project whose debug APK assembles with zero edits, directly with the SDK's build tools (store signing keys stay a manual step). Every platform's icons generate from one square source image. See [Packaging](/packaging).
|
||||
8. macOS signing supports `adhoc` and `identity` modes with entitlements; notarization is submitted manually with the platform tools after packaging. No signing tooling exists yet for the other platforms. See [Code Signing](/packaging/signing).
|
||||
9. The automation server is a file-based protocol the runtime serves on every desktop platform: snapshots, assertions, synthetic input, screenshots, record/replay. Engine screenshots render through the deterministic CPU reference renderer on every platform, so they are byte-comparable across hosts. Mobile exposes accessibility snapshots and actions through the embed ABI; the iOS and Android toolkit hosts serve the same file-based protocol inside the app's data container when launched with automation enabled.
|
||||
|
||||
@@ -25,7 +25,7 @@ Apps that [embed web content](/frontend) choose a web engine backend for their W
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td>In progress</td>
|
||||
<td>WebView2</td>
|
||||
<td>Not wired yet; builds fail early instead of silently using WebView2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -41,6 +41,8 @@ The intended parity contract is the same Zig app model, same runtime services, s
|
||||
|
||||
System mode has no bundled browser dependency. It uses the OS web engine, so rendering and web platform support follow the user's installed OS.
|
||||
|
||||
On Windows the system engine is WebView2, whose Evergreen runtime is preinstalled on Windows 11 and current Windows 10; older machines need the runtime installer from the WebView2 download page (`native doctor` checks for it). The build stages the small vendored `WebView2Loader.dll` next to the app executable automatically, and `native package` includes it in the Windows artifact.
|
||||
|
||||
## Chromium (CEF)
|
||||
|
||||
Set the app engine once:
|
||||
|
||||
@@ -80,6 +80,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the browser example");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -239,7 +240,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -272,6 +285,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium or target.result.os.tag != .macos) return;
|
||||
const copy = b.addSystemCommand(&.{
|
||||
|
||||
@@ -93,6 +93,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -259,7 +260,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -292,6 +305,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -256,7 +257,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -289,6 +302,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -256,7 +257,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -289,6 +302,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -256,7 +257,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -289,6 +302,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -256,7 +257,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -289,6 +302,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
+33
-1
@@ -108,6 +108,7 @@ pub fn build(b: *std.Build) void {
|
||||
const run = b.addRunArtifact(exe);
|
||||
run.step.dependOn(&frontend_build.step);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -304,7 +305,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -337,6 +350,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -108,6 +108,7 @@ pub fn build(b: *std.Build) void {
|
||||
const run = b.addRunArtifact(exe);
|
||||
run.step.dependOn(&frontend_build.step);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -304,7 +305,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -337,6 +350,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -108,6 +108,7 @@ pub fn build(b: *std.Build) void {
|
||||
const run = b.addRunArtifact(exe);
|
||||
run.step.dependOn(&frontend_build.step);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -304,7 +305,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -337,6 +350,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
+33
-1
@@ -108,6 +108,7 @@ pub fn build(b: *std.Build) void {
|
||||
const run = b.addRunArtifact(exe);
|
||||
run.step.dependOn(&frontend_build.step);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -304,7 +305,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -337,6 +350,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run.step);
|
||||
|
||||
@@ -256,7 +257,19 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
} else if (platform == .windows) {
|
||||
switch (web_engine) {
|
||||
.system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} }),
|
||||
.system => {
|
||||
// The vendored WebView2 SDK header (third_party/webview2)
|
||||
// turns on the host's embedded-WebView layer; the host
|
||||
// fails the compile by design if it cannot be found.
|
||||
app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
||||
// WebView2Loader.dll rides next to the installed app
|
||||
// executable: the host loads it at runtime to discover
|
||||
// the machine's WebView2 runtime. Canvas apps never
|
||||
// touch it.
|
||||
const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
b.getInstallStep().dependOn(&loader.step);
|
||||
},
|
||||
.chromium => {
|
||||
const cef_check = addCefCheck(b, target, cef_dir);
|
||||
if (cef_auto_install) {
|
||||
@@ -289,6 +302,25 @@ fn linkPlatform(b: *std.Build, target: std.Build.ResolvedTarget, app_mod: *std.B
|
||||
}
|
||||
}
|
||||
|
||||
/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
/// to the framework root.
|
||||
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
return if (target.result.cpu.arch == .aarch64)
|
||||
"third_party/webview2/arm64/WebView2Loader.dll"
|
||||
else
|
||||
"third_party/webview2/x64/WebView2Loader.dll";
|
||||
}
|
||||
|
||||
/// `zig build run` executes the cached artifact, which has no installed
|
||||
/// WebView2Loader.dll beside it; the vendored loader's directory goes on
|
||||
/// the run step's PATH so the host's LoadLibrary resolves it in dev runs.
|
||||
fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
if (web_engine != .system) return;
|
||||
if (target.result.os.tag != .windows) return;
|
||||
const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
}
|
||||
|
||||
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
if (web_engine != .chromium) return;
|
||||
if (target.result.os.tag != .macos) return;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"app.zon",
|
||||
"third_party/webview2",
|
||||
"native-sdk.d.ts",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
|
||||
@@ -18,6 +18,7 @@ const mirrors = [
|
||||
{ source: 'LICENSE', target: 'LICENSE' },
|
||||
{ source: 'skills', target: 'skills' },
|
||||
{ source: 'skill-data', target: 'skill-data' },
|
||||
{ source: 'third_party/webview2', target: 'third_party/webview2' },
|
||||
];
|
||||
|
||||
const errors = [];
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
// build script that `addApp` lives in), app.zon (the SDK's own manifest,
|
||||
// which its build script reads at configure time), assets/ (files the
|
||||
// build graph resolves from the dependency, e.g. the Windows application
|
||||
// manifest build/app.zig wires via dep.path), and the agent skills.
|
||||
// With the payload in the package, `native init && native dev` work
|
||||
// offline right after install.
|
||||
// manifest build/app.zig wires via dep.path), third_party/webview2/ (the
|
||||
// vendored WebView2 SDK header and loader the Windows build resolves the
|
||||
// same way; the CEF runtimes stay out — they are large downloaded
|
||||
// artifacts, not repo files), and the agent skills. With the payload in
|
||||
// the package, `native init && native dev` work offline right after
|
||||
// install.
|
||||
|
||||
import { cpSync, copyFileSync, rmSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
@@ -27,6 +30,14 @@ for (const dir of ['src', 'build', 'assets', 'skills', 'skill-data']) {
|
||||
console.log(`✓ Copied ${dir}/ to ${target}`);
|
||||
}
|
||||
|
||||
{
|
||||
const source = join(repoRoot, 'third_party', 'webview2');
|
||||
const target = join(projectRoot, 'third_party', 'webview2');
|
||||
rmSync(join(projectRoot, 'third_party'), { recursive: true, force: true });
|
||||
cpSync(source, target, { recursive: true });
|
||||
console.log(`✓ Copied third_party/webview2/ to ${target}`);
|
||||
}
|
||||
|
||||
for (const file of ['build.zig', 'build.zig.zon', 'app.zon', 'LICENSE']) {
|
||||
const source = join(repoRoot, file);
|
||||
const target = join(projectRoot, file);
|
||||
|
||||
@@ -27,19 +27,22 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
/* The WebView2 SDK header is vendored (third_party/webview2/include) and
|
||||
* every first-party build graph puts it on the include path, so a build
|
||||
* that cannot see it is misconfigured — fail it loudly instead of
|
||||
* shipping a host whose WebView loads report WebViewNotFound at runtime.
|
||||
* NATIVE_SDK_ALLOW_WEBVIEW2_STUB opts a hand-rolled build into the old
|
||||
* stubbed layer (canvas apps are unaffected by the stub). */
|
||||
#if __has_include(<WebView2.h>) && __has_include(<wrl.h>)
|
||||
#include <WebView2.h>
|
||||
#include <wrl.h>
|
||||
#define NATIVE_SDK_HAS_WEBVIEW2 1
|
||||
using Microsoft::WRL::Callback;
|
||||
using Microsoft::WRL::ComPtr;
|
||||
#else
|
||||
#elif defined(NATIVE_SDK_ALLOW_WEBVIEW2_STUB)
|
||||
#define NATIVE_SDK_HAS_WEBVIEW2 0
|
||||
/* Loud on purpose: without the WebView2 SDK header on the include path
|
||||
* the host builds with the embedded WebView layer stubbed out — canvas
|
||||
* apps are unaffected, but apps that load a WebView report
|
||||
* WebViewNotFound at start. */
|
||||
#pragma message("WebView2.h not found: building the Windows host without the embedded WebView layer (canvas apps unaffected; WebView loads will report WebViewNotFound)")
|
||||
#else
|
||||
#error "WebView2.h not found: add third_party/webview2/include to the include path, or define NATIVE_SDK_ALLOW_WEBVIEW2_STUB to build without the embedded WebView layer"
|
||||
#endif
|
||||
|
||||
/* Media Foundation (the audio backend below) + WinHTTP (the audio cache
|
||||
@@ -557,6 +560,9 @@ struct Host {
|
||||
int appearance_color_scheme = -1;
|
||||
int appearance_reduce_motion = -1;
|
||||
int appearance_high_contrast = -1;
|
||||
/* Whether the CoInitializeEx in native_sdk_windows_create succeeded
|
||||
* and native_sdk_windows_destroy owes the balancing CoUninitialize. */
|
||||
bool com_initialized = false;
|
||||
AudioState audio;
|
||||
AudioSpectrumState spectrum;
|
||||
std::shared_ptr<HostLifetime> lifetime = std::make_shared<HostLifetime>();
|
||||
@@ -3952,6 +3958,92 @@ static void destroyAllWindows(Host *host) {
|
||||
#if NATIVE_SDK_HAS_WEBVIEW2
|
||||
using CreateEnvironmentFn = HRESULT (STDAPICALLTYPE *)(PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions *, ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *);
|
||||
|
||||
/* The event-handler factory. The full WRL provides this as
|
||||
* Microsoft::WRL::Callback, but the mingw WRL subset only carries ComPtr,
|
||||
* so the handful of WebView2 completion and event handlers are built on
|
||||
* this minimal equivalent: one refcounted COM object per handler that
|
||||
* forwards Invoke to the wrapped callable. QueryInterface needs each
|
||||
* handler's IID at runtime; the constants mirror the interface
|
||||
* declarations in WebView2.h (mingw's __uuidof only covers interfaces
|
||||
* its own headers declare, the same reason the WIC GUIDs further down
|
||||
* are defined locally). */
|
||||
static const GUID kNativeSdkIID_EnvironmentCompletedHandler = {0x4e8a3389, 0xc9d8, 0x4bd2, {0xb6, 0xb5, 0x12, 0x4f, 0xee, 0x6c, 0xc1, 0x4d}};
|
||||
static const GUID kNativeSdkIID_ControllerCompletedHandler = {0x6c4819f3, 0xc9b7, 0x4260, {0x81, 0x27, 0xc9, 0xf5, 0xbd, 0xe7, 0xf6, 0x8c}};
|
||||
static const GUID kNativeSdkIID_WebMessageReceivedHandler = {0x57213f19, 0x00e6, 0x49fa, {0x8e, 0x07, 0x89, 0x8e, 0xa0, 0x1e, 0xcb, 0xd2}};
|
||||
static const GUID kNativeSdkIID_AcceleratorKeyPressedHandler = {0xb29c7e28, 0xfa79, 0x41a8, {0x8e, 0x44, 0x65, 0x81, 0x1c, 0x76, 0xdc, 0xb2}};
|
||||
static const GUID kNativeSdkIID_WebResourceRequestedHandler = {0xab00b74c, 0x15f1, 0x4646, {0x80, 0xe8, 0xe7, 0x63, 0x41, 0xd2, 0x5d, 0x71}};
|
||||
static const GUID kNativeSdkIID_NavigationStartingHandler = {0x9adbe429, 0xf36d, 0x432b, {0x9d, 0xdc, 0xf8, 0x88, 0x1f, 0xbd, 0x76, 0xe3}};
|
||||
|
||||
template <typename Interface> struct WebView2HandlerIid;
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_EnvironmentCompletedHandler; }
|
||||
};
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_ControllerCompletedHandler; }
|
||||
};
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2WebMessageReceivedEventHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_WebMessageReceivedHandler; }
|
||||
};
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2AcceleratorKeyPressedEventHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_AcceleratorKeyPressedHandler; }
|
||||
};
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2WebResourceRequestedEventHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_WebResourceRequestedHandler; }
|
||||
};
|
||||
template <> struct WebView2HandlerIid<ICoreWebView2NavigationStartingEventHandler> {
|
||||
static const GUID &value() { return kNativeSdkIID_NavigationStartingHandler; }
|
||||
};
|
||||
|
||||
/* Every handler interface above declares a two-argument Invoke; this
|
||||
* trait reads the argument types off the interface so the override below
|
||||
* matches exactly. */
|
||||
template <typename Method> struct WebView2InvokeSignature;
|
||||
template <typename Interface, typename FirstArg, typename SecondArg>
|
||||
struct WebView2InvokeSignature<HRESULT (STDMETHODCALLTYPE Interface::*)(FirstArg, SecondArg)> {
|
||||
using First = FirstArg;
|
||||
using Second = SecondArg;
|
||||
};
|
||||
|
||||
template <typename Interface, typename Callable>
|
||||
class WebView2Handler final : public Interface {
|
||||
public:
|
||||
explicit WebView2Handler(Callable callable) : callable_(std::move(callable)) {}
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **object) override {
|
||||
if (!object) return E_POINTER;
|
||||
if (IsEqualIID(riid, WebView2HandlerIid<Interface>::value()) || IsEqualIID(riid, IID_IUnknown)) {
|
||||
*object = static_cast<Interface *>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
*object = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE AddRef() override {
|
||||
return refs_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE Release() override {
|
||||
ULONG remaining = refs_.fetch_sub(1, std::memory_order_acq_rel) - 1;
|
||||
if (remaining == 0) delete this;
|
||||
return remaining;
|
||||
}
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
typename WebView2InvokeSignature<decltype(&Interface::Invoke)>::First first,
|
||||
typename WebView2InvokeSignature<decltype(&Interface::Invoke)>::Second second) override {
|
||||
return callable_(first, second);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> refs_{1};
|
||||
Callable callable_;
|
||||
};
|
||||
|
||||
template <typename Interface, typename Callable>
|
||||
static ComPtr<Interface> Callback(Callable callable) {
|
||||
ComPtr<Interface> handler;
|
||||
handler.Attach(new WebView2Handler<Interface, Callable>(std::move(callable)));
|
||||
return handler;
|
||||
}
|
||||
|
||||
static const wchar_t *nativeSdkBridgeScript() {
|
||||
return LR"ZN((function(){
|
||||
if(window.zero&&window.zero.invoke&&window.zero.on&&window.zero._emit){return;}
|
||||
@@ -4240,7 +4332,7 @@ static bool createChildWebView(Host *host, const std::string &key) {
|
||||
uint64_t bridge_window_id = found->second.window_id;
|
||||
std::string bridge_label = found->second.label;
|
||||
found->second.webview->add_WebMessageReceived(Callback<ICoreWebView2WebMessageReceivedEventHandler>(
|
||||
[host, bridge_window_id, bridge_label, lifetime](ICoreWebView2 *, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
|
||||
[host, key, bridge_window_id, bridge_label, lifetime](ICoreWebView2 *, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
|
||||
auto token = lifetime.lock();
|
||||
if (!token) return S_OK;
|
||||
std::lock_guard<std::recursive_mutex> guard(token->mutex);
|
||||
@@ -4902,6 +4994,15 @@ Host *native_sdk_windows_create(const char *app_name, size_t app_name_len, const
|
||||
InitCommonControlsEx(&controls);
|
||||
|
||||
Host *host = new Host();
|
||||
/* The WebView2 environment factory requires a single-threaded
|
||||
* apartment on the calling thread; without one it fails with
|
||||
* CO_E_NOTINITIALIZED. Create, run, and destroy all execute on the
|
||||
* app's main thread, so one init here pairs with the CoUninitialize
|
||||
* at the end of native_sdk_windows_destroy — after the controllers
|
||||
* are closed. S_FALSE (already initialized) still pairs;
|
||||
* RPC_E_CHANGED_MODE (an MTA got there first) leaves nothing to
|
||||
* balance. */
|
||||
host->com_initialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
|
||||
host->app_name = slice(app_name, app_name_len);
|
||||
host->window_title = slice(window_title, window_title_len);
|
||||
host->bundle_id = slice(bundle_id, bundle_id_len);
|
||||
@@ -4945,7 +5046,9 @@ void native_sdk_windows_destroy(Host *host) {
|
||||
audioReleaseSession(host, true);
|
||||
removeNotificationIcon(host);
|
||||
destroyAllWindows(host);
|
||||
const bool com_initialized = host->com_initialized;
|
||||
delete host;
|
||||
if (com_initialized) CoUninitialize();
|
||||
}
|
||||
|
||||
void native_sdk_windows_run(Host *host, EventCallback callback, void *context) {
|
||||
|
||||
@@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const android_tool = @import("android.zig");
|
||||
const app_icon_tool = @import("app_icon");
|
||||
const assets_tool = @import("assets.zig");
|
||||
const buildgraph = @import("buildgraph.zig");
|
||||
const cef = @import("cef.zig");
|
||||
const codesign = @import("codesign.zig");
|
||||
const diagnostics = @import("diagnostics");
|
||||
@@ -212,6 +213,11 @@ fn createDesktopArtifact(allocator: std.mem.Allocator, io: std.Io, options: Pack
|
||||
const binary_subpath = try std.fmt.allocPrint(allocator, "bin/{s}", .{executable_name});
|
||||
defer allocator.free(binary_subpath);
|
||||
try copyFileToDir(allocator, io, dir, binary_path, binary_subpath);
|
||||
if (options.target == .windows and options.web_engine == .system) {
|
||||
try copyWindowsWebView2Loader(allocator, io, dir, options, binary_path);
|
||||
}
|
||||
} else if (options.target == .windows and options.web_engine == .system) {
|
||||
try writeFile(dir, io, "bin/README.txt", "Build the app binary separately and place it here for this target, together with the WebView2Loader.dll for its architecture (vendored in the SDK under third_party/webview2/).\n");
|
||||
} else {
|
||||
try writeFile(dir, io, "bin/README.txt", "Build the app binary separately and place it here for this target.\n");
|
||||
}
|
||||
@@ -934,6 +940,45 @@ fn zonStringAlloc(allocator: std.mem.Allocator, value: []const u8) ![]const u8 {
|
||||
return out.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
/// The Windows system engine discovers the machine's WebView2 runtime
|
||||
/// through WebView2Loader.dll, which the host loads from the executable's
|
||||
/// directory — the vendored copy ships inside every packaged app. The
|
||||
/// architecture comes from the packaged binary's PE header so an arm64
|
||||
/// build gets the arm64 loader.
|
||||
fn copyWindowsWebView2Loader(allocator: std.mem.Allocator, io: std.Io, dir: std.Io.Dir, options: PackageOptions, binary_path: []const u8) !void {
|
||||
const framework_root = blk: {
|
||||
if (options.env_map) |env_map| {
|
||||
if (try buildgraph.resolveFrameworkRoot(allocator, io, env_map)) |root| break :blk root;
|
||||
} else if (try buildgraph.frameworkRootFromExecutable(allocator, io)) |root| {
|
||||
break :blk root;
|
||||
}
|
||||
return error.MissingFramework;
|
||||
};
|
||||
defer allocator.free(framework_root);
|
||||
const arch_dir: []const u8 = if (try peExecutableIsArm64(io, binary_path)) "arm64" else "x64";
|
||||
const loader_path = try std.fs.path.join(allocator, &.{ framework_root, "third_party", "webview2", arch_dir, "WebView2Loader.dll" });
|
||||
defer allocator.free(loader_path);
|
||||
try copyFileToDir(allocator, io, dir, loader_path, "bin/WebView2Loader.dll");
|
||||
}
|
||||
|
||||
/// Whether a PE executable targets arm64, read from the COFF machine
|
||||
/// field. Anything unrecognized falls back to x64, the default Windows
|
||||
/// build target.
|
||||
fn peExecutableIsArm64(io: std.Io, path: []const u8) !bool {
|
||||
var file = try std.Io.Dir.cwd().openFile(io, path, .{});
|
||||
defer file.close(io);
|
||||
var read_buffer: [4096]u8 = undefined;
|
||||
var reader = file.reader(io, &read_buffer);
|
||||
var header: [4096]u8 = undefined;
|
||||
const len = try reader.interface.readSliceShort(&header);
|
||||
if (len < 0x40 or header[0] != 'M' or header[1] != 'Z') return false;
|
||||
const pe_offset: usize = std.mem.readInt(u32, header[0x3c..0x40], .little);
|
||||
if (pe_offset + 6 > len) return false;
|
||||
if (!std.mem.eql(u8, header[pe_offset..][0..4], "PE\x00\x00")) return false;
|
||||
const machine = std.mem.readInt(u16, header[pe_offset + 4 ..][0..2], .little);
|
||||
return machine == 0xaa64;
|
||||
}
|
||||
|
||||
fn copyFileToDir(allocator: std.mem.Allocator, io: std.Io, dir: std.Io.Dir, source_path: []const u8, dest_subpath: []const u8) !void {
|
||||
_ = allocator;
|
||||
try std.Io.Dir.copyFile(std.Io.Dir.cwd(), source_path, dir, dest_subpath, io, .{ .make_path = true, .replace = true });
|
||||
|
||||
@@ -944,11 +944,13 @@ fn buildZig(allocator: std.mem.Allocator, names: TemplateNames, framework_path:
|
||||
\\ const run = b.addRunArtifact(exe);
|
||||
\\ run.step.dependOn(&frontend_build.step);
|
||||
\\ addCefRuntimeRunFiles(b, target, run, exe, web_engine, cef_dir);
|
||||
\\ addWebView2RuntimeRunFiles(b, target, run, web_engine, native_sdk_path);
|
||||
\\ const run_step = b.step("run", "Run the app");
|
||||
\\ run_step.dependOn(&run.step);
|
||||
\\
|
||||
\\ const dev = b.addSystemCommand(&.{ "native", "dev", "--manifest", "app.zon", "--binary" });
|
||||
\\ dev.addFileArg(exe.getEmittedBin());
|
||||
\\ addWebView2RuntimeRunFiles(b, target, dev, web_engine, native_sdk_path);
|
||||
\\ dev.step.dependOn(&exe.step);
|
||||
\\ dev.step.dependOn(&frontend_install.step);
|
||||
\\ const dev_step = b.step("dev", "Run the frontend dev server and native shell");
|
||||
@@ -972,6 +974,11 @@ fn buildZig(allocator: std.mem.Allocator, names: TemplateNames, framework_path:
|
||||
\\ b.fmt("zig-out/package/{s}-0.1.0-{s}-{s}{s}", .{ app_exe_name, @tagName(package_target), optimize_name, packageSuffix(package_target) }),
|
||||
\\ "--binary",
|
||||
\\ });
|
||||
\\ // The CLI resolves SDK-owned package inputs (the vendored WebView2
|
||||
\\ // loader) from the framework root; a PATH-resolved `native` could
|
||||
\\ // belong to a different checkout than the one this build compiled
|
||||
\\ // against, so hand the same root over explicitly.
|
||||
\\ package.setEnvironmentVariable("NATIVE_SDK_PATH", b.pathFromRoot(native_sdk_path));
|
||||
\\ package.addFileArg(exe.getEmittedBin());
|
||||
\\ package.addArgs(&.{ "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir });
|
||||
\\ if (cef_auto_install) package.addArg("--cef-auto-install");
|
||||
@@ -1139,7 +1146,19 @@ fn buildZig(allocator: std.mem.Allocator, names: TemplateNames, framework_path:
|
||||
\\ if (web_engine == .chromium) app_mod.linkSystemLibrary("stdc++", .{});
|
||||
\\ } else if (platform == .windows) {
|
||||
\\ switch (web_engine) {
|
||||
\\ .system => app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{ "-std=c++17" } }),
|
||||
\\ .system => {
|
||||
\\ // The vendored WebView2 SDK header (third_party/webview2)
|
||||
\\ // turns on the host's embedded-WebView layer; the host
|
||||
\\ // fails the compile by design if it cannot be found.
|
||||
\\ app_mod.addIncludePath(nativeSdkPath(b, native_sdk_path, "third_party/webview2/include"));
|
||||
\\ app_mod.addCSourceFile(.{ .file = nativeSdkPath(b, native_sdk_path, "src/platform/windows/webview2_host.cpp"), .flags = &.{ "-std=c++17" } });
|
||||
\\ // WebView2Loader.dll rides next to the installed app
|
||||
\\ // executable: the host loads it at runtime to discover
|
||||
\\ // the machine's WebView2 runtime. Canvas apps never
|
||||
\\ // touch it.
|
||||
\\ const loader = b.addInstallBinFile(nativeSdkPath(b, native_sdk_path, webView2LoaderSubPath(target)), "WebView2Loader.dll");
|
||||
\\ b.getInstallStep().dependOn(&loader.step);
|
||||
\\ },
|
||||
\\ .chromium => {
|
||||
\\ const cef_check = addCefCheck(b, target, cef_dir);
|
||||
\\ if (cef_auto_install) {
|
||||
@@ -1172,6 +1191,26 @@ fn buildZig(allocator: std.mem.Allocator, names: TemplateNames, framework_path:
|
||||
\\ }
|
||||
\\}
|
||||
\\
|
||||
\\/// The vendored WebView2Loader.dll for the target architecture, relative
|
||||
\\/// to the framework root.
|
||||
\\fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
||||
\\ return if (target.result.cpu.arch == .aarch64)
|
||||
\\ "third_party/webview2/arm64/WebView2Loader.dll"
|
||||
\\ else
|
||||
\\ "third_party/webview2/x64/WebView2Loader.dll";
|
||||
\\}
|
||||
\\
|
||||
\\/// `zig build run` and `zig build dev` execute the cached artifact, which
|
||||
\\/// has no installed WebView2Loader.dll beside it; the vendored loader's
|
||||
\\/// directory goes on the step's PATH so the host's LoadLibrary resolves it
|
||||
\\/// (`native dev` passes its environment on to the app it spawns).
|
||||
\\fn addWebView2RuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, web_engine: WebEngineOption, native_sdk_path: []const u8) void {
|
||||
\\ if (web_engine != .system) return;
|
||||
\\ if (target.result.os.tag != .windows) return;
|
||||
\\ const loader_dir = std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
||||
\\ run.addPathDir(b.pathFromRoot(b.pathJoin(&.{ native_sdk_path, loader_dir })));
|
||||
\\}
|
||||
\\
|
||||
\\fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
||||
\\ if (web_engine != .chromium) return;
|
||||
\\ if (target.result.os.tag != .macos) return;
|
||||
@@ -2835,6 +2874,8 @@ test "writeDefaultApp emits Vite project files" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "frontend_build.step.dependOn(&frontend_install.step)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "\"native\", \"dev\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "dev.step.dependOn(&frontend_install.step)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "addWebView2RuntimeRunFiles(b, target, dev, web_engine, native_sdk_path)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "package.setEnvironmentVariable(\"NATIVE_SDK_PATH\", b.pathFromRoot(native_sdk_path))") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "chromium") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "cef-dir") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, build_zig_text, "src/platform/macos/cef_host.mm") != null);
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* The name of Microsoft Corporation, or the names of its contributors
|
||||
may not be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
# WebView2 SDK Vendor Layout
|
||||
|
||||
The Windows system web engine embeds the OS WebView2 runtime. The interface header and the loader library that discovers the installed runtime come from the WebView2 SDK NuGet package and are vendored here so a repo checkout builds the real embedded-WebView Windows host with no extra fetch step.
|
||||
|
||||
Vendored from the `Microsoft.Web.WebView2` NuGet package, version `1.0.2903.40` (package sha256 `ef128016dd1e51c59178c827ed5b8aa3322c57afa8675d930f8109505542ad74`), under the package's BSD-style license (`LICENSE.txt`, kept verbatim alongside the files it covers):
|
||||
|
||||
```text
|
||||
third_party/webview2/
|
||||
LICENSE.txt package LICENSE.txt, verbatim
|
||||
include/WebView2.h build/native/include/WebView2.h, verbatim
|
||||
x64/WebView2Loader.dll build/native/x64/WebView2Loader.dll, verbatim
|
||||
arm64/WebView2Loader.dll build/native/arm64/WebView2Loader.dll, verbatim
|
||||
```
|
||||
|
||||
`include/EventToken.h` is a first-party compatibility shim (see its header comment), not a package file.
|
||||
|
||||
The build adds `include/` to the include path of the Windows host and stages the architecture's `WebView2Loader.dll` next to the app executable; the host loads it at runtime to locate the installed WebView2 runtime (preinstalled on stock Windows 11; on older machines the Evergreen runtime installer provides it). Apps that never load a WebView run fine without either piece.
|
||||
|
||||
To update: download a newer `Microsoft.Web.WebView2` package from NuGet, replace the files listed above verbatim, and record the new version and package sha256 here.
|
||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
/* First-party compatibility shim, not part of the WebView2 SDK package:
|
||||
* WebView2.h includes "EventToken.h" for this one struct, and the
|
||||
* mingw-w64 headers zig ships do not carry that file. The layout is the
|
||||
* OS ABI's event-registration cookie — a single 64-bit value. */
|
||||
#ifndef __EVENTTOKEN_H__
|
||||
#define __EVENTTOKEN_H__
|
||||
|
||||
typedef struct EventRegistrationToken {
|
||||
__int64 value;
|
||||
} EventRegistrationToken;
|
||||
|
||||
#endif
|
||||
+60636
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
@@ -277,10 +277,10 @@ pub fn main(init: std.process.Init) !void {
|
||||
}
|
||||
} else if (std.mem.eql(u8, command, "package-windows")) {
|
||||
checkPackageShortcutFlags(command, args[2..]);
|
||||
try packageShortcut(allocator, init.io, args, .windows, "zig-out/package/windows");
|
||||
try packageShortcut(allocator, init.io, init.environ_map, args, .windows, "zig-out/package/windows");
|
||||
} else if (std.mem.eql(u8, command, "package-linux")) {
|
||||
checkPackageShortcutFlags(command, args[2..]);
|
||||
try packageShortcut(allocator, init.io, args, .linux, "zig-out/package/linux");
|
||||
try packageShortcut(allocator, init.io, init.environ_map, args, .linux, "zig-out/package/linux");
|
||||
} else if (std.mem.eql(u8, command, "package-ios")) {
|
||||
checkPackageShortcutFlags(command, args[2..]);
|
||||
const metadata = try tooling.manifest.readMetadata(allocator, init.io, try flagValue(args, "--manifest") orelse "app.zon");
|
||||
@@ -296,6 +296,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
.frontend = metadata.frontend,
|
||||
.web_engine = web_engine.engine,
|
||||
.cef_dir = web_engine.cef_dir,
|
||||
.env_map = init.environ_map,
|
||||
});
|
||||
tooling.package.printDiagnostic(stats);
|
||||
} else if (std.mem.eql(u8, command, "package-android")) {
|
||||
@@ -759,7 +760,7 @@ fn splitCommand(allocator: std.mem.Allocator, value: []const u8) ![]const []cons
|
||||
return parts.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
fn packageShortcut(allocator: std.mem.Allocator, io: std.Io, args: []const []const u8, target: tooling.package.PackageTarget, default_output: []const u8) !void {
|
||||
fn packageShortcut(allocator: std.mem.Allocator, io: std.Io, env_map: *std.process.Environ.Map, args: []const []const u8, target: tooling.package.PackageTarget, default_output: []const u8) !void {
|
||||
const metadata = try tooling.manifest.readMetadata(allocator, io, try flagValue(args, "--manifest") orelse "app.zon");
|
||||
const web_engine = try tooling.web_engine.resolve(.{ .web_engine = metadata.web_engine, .cef = metadata.cef }, .{});
|
||||
const stats = try tooling.package.createPackage(allocator, io, .{
|
||||
@@ -771,6 +772,7 @@ fn packageShortcut(allocator: std.mem.Allocator, io: std.Io, args: []const []con
|
||||
.frontend = metadata.frontend,
|
||||
.web_engine = web_engine.engine,
|
||||
.cef_dir = web_engine.cef_dir,
|
||||
.env_map = env_map,
|
||||
});
|
||||
tooling.package.printDiagnostic(stats);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user