This commit is contained in:
Florian Rappl
2026-09-04 14:05:36 +02:00
parent 042d8e6c70
commit 7cd552a78f
14 changed files with 159 additions and 17 deletions

View File

@@ -10,6 +10,8 @@
- Fixed cross-compilation behavior on same platform (#1098) @epsnm
- Fixed resolution of unrelated target (#1099) @epsnm
- Added target framework customization (#1095) @epsnm
- Added configurable Electron root directory for custom packaging layouts (#1106) @DYH1319
- Added build extensibility properties `ElectronSkipExecCommands` and `ElectronIntermediatePublishDir` (#1106) @DYH1319
# 0.5.2

View File

@@ -28,9 +28,53 @@ These are the current default values when you don't make any changes:
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
<Title>$(MSBuildProjectName)</Title>
<ElectronRootDir>../..</ElectronRootDir>
<ElectronSkipExecCommands>false</ElectronSkipExecCommands>
</PropertyGroup>
```
### Custom Packaging Layout
`ElectronRootDir` tells the .NET app where to find the Electron binary when it is started first (DotNet-First startup of a packaged app). The path is resolved relative to the directory of the .NET executable; absolute paths are used as-is.
The default `../..` matches the standard Electron-First packaging layout produced by `dotnet publish`, where the .NET app is placed in `resources/bin` of the Electron package:
```
<install-root>/
MyApp # Electron executable
resources/
bin/
MyApp # .NET executable
```
If your own packaging pipeline places the .NET executable at the package root and Electron in a subdirectory, set `ElectronRootDir` accordingly:
```xml
<ElectronRootDir>electron</ElectronRootDir>
```
```
<install-root>/
MyApp # .NET executable
electron/
MyApp # Electron executable
resources/
locales/
```
The property is also used to detect whether the app runs packaged or unpackaged, so it must point to the directory that actually contains the Electron binary.
### Build Extensibility
These properties are useful when Electron.NET is embedded into a custom build or CI pipeline:
| Property | Default | Description |
| --- | --- | --- |
| `ElectronSkipExecCommands` | `false` | Skips all `npm`/`npx` invocations of the Electron build and publish targets. Use it when the dependencies and the Electron distribution are provided by the pipeline itself (for example from a cache or an offline mirror). |
| `ElectronIntermediatePublishDir` | `$(IntermediateOutputPath)PubTmp\` | Overrides the intermediate directory the .NET app is published to before it is assembled into the Electron package. |
`ElectronPackageId` and `Title` only fall back to the project name when they are not already set, so both can be defined by a `Directory.Build.props` or passed on the command line.
### Relation to package.json
ElectronNET.Core does not work with an `electron-manifest.json` file anymore.

View File

@@ -222,6 +222,15 @@ The publish process will:
> macOS builds can't be created on Windows machines because they require symlinks that aren't supported on Windows (per [this Electron issue](https://github.com/electron-userland/electron-packager/issues/71)). macOS builds can be produced on either Linux or macOS machines.
## Custom Packaging Pipelines
The default layout is Electron-First: electron-builder produces the Electron package and the .NET app is placed in `resources/bin`. If you assemble the package yourself and put the .NET executable at the package root with Electron in a subdirectory, set `ElectronRootDir` so the .NET app can locate the Electron binary at runtime.
For pipelines that provide the npm dependencies themselves, `ElectronSkipExecCommands` suppresses all `npm`/`npx` invocations, and `ElectronIntermediatePublishDir` redirects the intermediate publish output.
See [Configuration](Configuration.md#custom-packaging-layout) for details.
## 🚀 Next Steps
- **[Startup Methods](Startup-Methods.md)** - Understanding different launch modes for packaged apps

View File

@@ -37,10 +37,18 @@
public ProcessRunner(string name)
{
this.Name = name;
this.ExitWaitTimeout = DefaultExitWaitTimeout;
}
public event EventHandler<EventArgs> ProcessExited;
/// <summary>Gets or sets the value <see cref="ExitWaitTimeout"/> is initialized with.</summary>
public static int DefaultExitWaitTimeout { get; set; } = 5000;
/// <summary>Gets or sets the time in milliseconds to wait for the process and its I/O to finish
/// after the exited event has been raised. Values of zero or less wait indefinitely.</summary>
public int ExitWaitTimeout { get; set; }
public bool IsDisposed { get; private set; }
private Process Process
@@ -498,8 +506,17 @@
{
try
{
var timeout = this.ExitWaitTimeout;
// This shouldn't throw here, but the mono process implementation doesn't always behave as it should.
this.process.WaitForExit();
if (timeout > 0)
{
this.process.WaitForExit(timeout);
}
else
{
this.process.WaitForExit();
}
}
catch (Exception ex)
{

View File

@@ -6,6 +6,8 @@
public string ElectronVersion { get; internal set; }
public string ElectronRootDir { get; internal set; }
public string RuntimeIdentifier { get; internal set; }
public bool ElectronSingleInstance { get; internal set; }

View File

@@ -0,0 +1,31 @@
namespace ElectronNET.Runtime.Helpers
{
using System;
using System.IO;
/// <summary>
/// Resolves the directory that contains the Electron binary of a packaged application.
/// </summary>
internal static class ElectronRootDirResolver
{
/// <summary>The location of the Electron binary relative to the .NET application directory in the
/// default (Electron-first) layout, where the .NET application is placed in 'resources/bin'.</summary>
internal const string DefaultElectronRootDir = "../..";
/// <summary>Resolves the Electron root directory relative to the given .NET application directory.</summary>
/// <param name="baseDirectory">The directory containing the .NET application.</param>
/// <returns>The directory containing the Electron binary.</returns>
public static DirectoryInfo Resolve(DirectoryInfo baseDirectory)
{
var rootDir = ElectronNetRuntime.BuildInfo?.ElectronRootDir;
if (string.IsNullOrWhiteSpace(rootDir))
{
rootDir = DefaultElectronRootDir;
}
// An absolute ElectronRootDir is used as-is by Path.Combine.
return new DirectoryInfo(Path.GetFullPath(Path.Combine(baseDirectory.FullName, rootDir)));
}
}
}

View File

@@ -69,6 +69,15 @@
return false;
}
// Packaged, if the Electron binary is found where ElectronRootDir points to.
var electronExecutable = ElectronNetRuntime.ElectronExecutable;
if (!string.IsNullOrEmpty(electronExecutable) &&
File.Exists(Path.Combine(ElectronRootDirResolver.Resolve(dir).FullName, electronExecutable)))
{
return false;
}
if (dir.GetDirectories().Any(e => e.Name == ".electron"))
{
return true;

View File

@@ -11,6 +11,7 @@
using System.Threading.Tasks;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Helpers;
/// <summary>
/// Launches and manages the Electron app process.
@@ -87,7 +88,7 @@
}
else
{
dir = dir.Parent!.Parent!;
dir = ElectronRootDirResolver.Resolve(dir);
startCmd = Path.Combine(dir.FullName, this.electronBinaryName);
args = $"-dotnetpacked -electronforcedport={this.socketPort:D} " + this.extraArguments;
workingDir = dir.FullName;

View File

@@ -178,6 +178,7 @@
{
buildInfo.ElectronExecutable = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronExecutable))?.Value;
buildInfo.ElectronVersion = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronVersion))?.Value;
buildInfo.ElectronRootDir = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronRootDir))?.Value;
buildInfo.RuntimeIdentifier = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.RuntimeIdentifier))?.Value;
buildInfo.Title = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Title))?.Value;
buildInfo.Version = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Version))?.Value;

View File

@@ -11,9 +11,14 @@
<ElectronSplashScreen></ElectronSplashScreen>
<ElectronIcon></ElectronIcon>
<PackageIcon></PackageIcon>
<ElectronPackageId>$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
<ElectronPackageId Condition="'$(ElectronPackageId)' == ''">$(MSBuildProjectName.Replace(".", "-").ToLower())</ElectronPackageId>
<ElectronBuilderJson>electron-builder.json</ElectronBuilderJson>
<Title>$(MSBuildProjectName)</Title>
<Title Condition="'$(Title)' == ''">$(MSBuildProjectName)</Title>
<!-- Location of the Electron binary relative to the .NET application directory of a packaged app.
The default matches the standard layout, where the .NET app is placed in 'resources/bin'. -->
<ElectronRootDir Condition="'$(ElectronRootDir)' == ''">../..</ElectronRootDir>
<!-- Set to true to suppress the npm/npx invocations of the Electron build and publish targets. -->
<ElectronSkipExecCommands Condition="'$(ElectronSkipExecCommands)' == ''">false</ElectronSkipExecCommands>
</PropertyGroup>
<PropertyGroup>

View File

@@ -19,6 +19,7 @@
<ItemGroup>
<AssemblyMetadata Include="ElectronExecutable" Value="$(ElectronExecutable)" />
<AssemblyMetadata Include="ElectronVersion" Value="$(ElectronVersion)" />
<AssemblyMetadata Include="ElectronRootDir" Value="$(ElectronRootDir)" />
<AssemblyMetadata Include="RuntimeIdentifier" Value="$(RuntimeIdentifier)" />
<AssemblyMetadata Include="ElectronSingleInstance" Value="$(ElectronSingleInstance)" />
<AssemblyMetadata Include="Title" Value="$(Title)" />

View File

@@ -170,7 +170,9 @@
<PropertyGroup Condition="'$(UsingMicrosoftNETSdkWeb)' != 'true'">
<_NonIntermediatePublishDir>$(PublishDir)</_NonIntermediatePublishDir>
<PublishUrl>$(PublishDir)</PublishUrl>
<PublishDir>$(IntermediateOutputPath)PubTmp\</PublishDir>
<!-- ElectronIntermediatePublishDir allows overriding the intermediate publish directory -->
<PublishDir Condition="'$(ElectronIntermediatePublishDir)' != ''">$(ElectronIntermediatePublishDir)</PublishDir>
<PublishDir Condition="'$(ElectronIntermediatePublishDir)' == ''">$(IntermediateOutputPath)PubTmp\</PublishDir>
</PropertyGroup>
<RemoveDir Directories="$(PublishDir);$(PublishUrl)" Condition="'$(UsingMicrosoftNETSdkWeb)' != 'true'" />
@@ -459,13 +461,13 @@
<!--<Message Importance="High" Text="ElectronConfigureApp: CopiedFiles - @(_CopiedFiles)" />-->
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="@(_CopiedFiles->Count()) > 0" />
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(_CopiedFiles->Count()) > 0" />
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(_CopiedFiles->Count()) > 0">
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(_CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
<PropertyGroup>
<_NpmCmd>node node_modules/electron/install.js</_NpmCmd>
@@ -474,13 +476,13 @@
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
</PropertyGroup>
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="$(ElectronMajor) &gt;= 42 AND @(_CopiedFiles->Count()) > 0" />
<Message Importance="High" Text="Running command: $(_NpmCmd) in folder: $(ElectronOutputPath)" Condition="'$(ElectronSkipExecCommands)' != 'true' AND $(ElectronMajor) &gt;= 42 AND @(_CopiedFiles->Count()) > 0" />
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="$(ElectronMajor) &gt;= 42 AND @(_CopiedFiles->Count()) > 0">
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronOutputPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND $(ElectronMajor) &gt;= 42 AND @(_CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron installation failed!" Condition="'$(ExecExitCode)' != '0'" />
<Message Importance="High" Text="Electron installation failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
<!-- Fix up incorrect symlinks created by npm on Windows when targeting macOS -->
<PropertyGroup>
@@ -615,11 +617,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpmAppDepsCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmAppDepsCmd)'</_NpmAppDepsCmd>
</PropertyGroup>
<Exec Command="$(_NpmAppDepsCmd)" WorkingDirectory="$(ElectronAppDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Exec Command="$(_NpmAppDepsCmd)" WorkingDirectory="$(ElectronAppDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron app dependency install failed!" Condition="'$(ExecExitCode)' != '0'" />
<Message Importance="High" Text="Electron app dependency install failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
<!-- Install electron-builder (the dev tool) into the publish root, which is the
electron-builder projectDir (cwd). app metadata + runtime deps are read from
@@ -645,11 +647,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
</PropertyGroup>
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
<!-- Transform ElectronPublishUrlFullPath to a wslpath if IsLinuxWsl is true -->
<Exec Command="wsl wslpath '$(ElectronPublishUrlFullPath)'" ConsoleToMsBuild="true" Condition="'$(IsLinuxWsl)' == 'true'">
@@ -673,11 +675,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'</_NpxCmd>
</PropertyGroup>
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="1800000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="1800000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="'$(ElectronSkipExecCommands)' != 'true' AND @(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '0'" />
<Message Importance="High" Text="Electron setup failed!" Condition="'$(ExecExitCode)' != '' AND '$(ExecExitCode)' != '0'" />
</Target>

View File

@@ -553,6 +553,24 @@
Category="General">
</StringProperty>
<StringProperty Name="ElectronRootDir"
DisplayName="Electron Root Directory"
Description="Location of the Electron binary relative to the .NET application directory of a packaged app. Default: ../.. (the .NET app lives in resources/bin)"
Category="General">
</StringProperty>
<BoolProperty Name="ElectronSkipExecCommands"
DisplayName="Skip npm/npx Commands"
Description="If enabled, the npm and npx invocations of the Electron build and publish targets are skipped"
Category="General">
</BoolProperty>
<StringProperty Name="ElectronIntermediatePublishDir"
DisplayName="Intermediate Publish Directory"
Description="Overrides the intermediate publish directory used while packaging. Default: $(IntermediateOutputPath)PubTmp\"
Category="General">
</StringProperty>
<StringProperty Name="ElectronSplashScreen"
DisplayName="Splash Screen Image"
Description="Choose a PNG image to be shown as splash screen on startup"