mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
214 lines
7.3 KiB
C#
214 lines
7.3 KiB
C#
using LanMountainDesktop.Shared.Contracts.Launcher;
|
|
|
|
namespace LanMountainDesktop.Launcher.Deployment;
|
|
|
|
internal sealed record HostLaunchPlan(
|
|
string HostPath,
|
|
string PackageRoot,
|
|
string WorkingDirectory,
|
|
IReadOnlyList<string> Arguments,
|
|
IReadOnlyDictionary<string, string> EnvironmentVariables,
|
|
AppVersionInfo VersionInfo);
|
|
|
|
internal static class HostLaunchPlanBuilder
|
|
{
|
|
public const string DataRootOptionName = "data-root";
|
|
|
|
private static readonly string[] LauncherOnlyOptions =
|
|
[
|
|
"debug", "show-loading-details", "plugins-dir", "source", "result",
|
|
"app-root", DataRootOptionName,
|
|
LauncherIpcConstants.LauncherPidEnvVar,
|
|
LauncherIpcConstants.PackageRootEnvVar,
|
|
LauncherIpcConstants.VersionEnvVar,
|
|
LauncherIpcConstants.CodenameEnvVar
|
|
];
|
|
|
|
public static HostLaunchPlan Build(
|
|
CommandContext context,
|
|
DeploymentLocator deploymentLocator,
|
|
HostResolutionResult resolution,
|
|
string? dataRoot = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
ArgumentNullException.ThrowIfNull(deploymentLocator);
|
|
ArgumentNullException.ThrowIfNull(resolution);
|
|
|
|
if (string.IsNullOrWhiteSpace(resolution.ResolvedHostPath))
|
|
{
|
|
throw new InvalidOperationException("Host path must be resolved before building a launch plan.");
|
|
}
|
|
|
|
var hostPath = Path.GetFullPath(resolution.ResolvedHostPath);
|
|
var packageRoot = ResolvePackageRoot(hostPath, resolution.AppRoot, resolution.ResolutionSource);
|
|
var versionInfo = deploymentLocator.GetVersionInfo();
|
|
var arguments = BuildForwardedArguments(context, packageRoot, versionInfo, dataRoot);
|
|
var environment = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
[LauncherIpcConstants.LauncherPidEnvVar] = Environment.ProcessId.ToString(),
|
|
[LauncherIpcConstants.PackageRootEnvVar] = packageRoot,
|
|
[LauncherIpcConstants.VersionEnvVar] = versionInfo.Version,
|
|
[LauncherIpcConstants.CodenameEnvVar] = versionInfo.Codename
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(dataRoot))
|
|
{
|
|
environment["LMD_DATA_ROOT"] = dataRoot;
|
|
}
|
|
|
|
return new HostLaunchPlan(
|
|
hostPath,
|
|
packageRoot,
|
|
Directory.Exists(packageRoot)
|
|
? packageRoot
|
|
: Path.GetDirectoryName(hostPath) ?? AppContext.BaseDirectory,
|
|
arguments,
|
|
environment,
|
|
versionInfo);
|
|
}
|
|
|
|
public static string FormatArgumentsForLog(IReadOnlyList<string> arguments)
|
|
{
|
|
return string.Join(" ", arguments.Select(QuoteArgument));
|
|
}
|
|
|
|
private static string ResolvePackageRoot(string hostPath, string appRoot, string? resolutionSource)
|
|
{
|
|
var fullAppRoot = string.IsNullOrWhiteSpace(appRoot)
|
|
? AppContext.BaseDirectory
|
|
: Path.GetFullPath(appRoot);
|
|
|
|
var hostDirectory = Path.GetDirectoryName(hostPath);
|
|
if (hostDirectory is not null &&
|
|
Directory.Exists(fullAppRoot) &&
|
|
IsAppDeploymentDirectory(hostDirectory) &&
|
|
IsParentOf(fullAppRoot, hostDirectory))
|
|
{
|
|
return fullAppRoot;
|
|
}
|
|
|
|
if (string.Equals(resolutionSource, "published_deployment", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(resolutionSource, "explicit_app_root_deployment", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(resolutionSource, "legacy_fallback", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return fullAppRoot;
|
|
}
|
|
|
|
return hostDirectory ?? fullAppRoot;
|
|
}
|
|
|
|
private static IReadOnlyList<string> BuildForwardedArguments(
|
|
CommandContext context,
|
|
string packageRoot,
|
|
AppVersionInfo versionInfo,
|
|
string? dataRoot = null)
|
|
{
|
|
var arguments = new List<string>();
|
|
|
|
for (var index = 0; index < context.RawArgs.Count; index++)
|
|
{
|
|
var arg = context.RawArgs[index];
|
|
|
|
if (index == 0 &&
|
|
!arg.StartsWith("--", StringComparison.Ordinal) &&
|
|
string.Equals(arg, context.Command, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (index == 1 &&
|
|
!arg.StartsWith("--", StringComparison.Ordinal) &&
|
|
string.Equals(arg, context.SubCommand, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (arg.StartsWith("--", StringComparison.Ordinal))
|
|
{
|
|
var key = arg[2..];
|
|
var equalsIndex = key.IndexOf('=');
|
|
if (equalsIndex >= 0)
|
|
{
|
|
key = key[..equalsIndex];
|
|
}
|
|
|
|
if (LauncherOnlyOptions.Contains(key, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
if (equalsIndex < 0 &&
|
|
index + 1 < context.RawArgs.Count &&
|
|
!context.RawArgs[index + 1].StartsWith("--", StringComparison.Ordinal))
|
|
{
|
|
index++;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
arguments.Add(arg);
|
|
}
|
|
|
|
arguments.Add($"--{LauncherIpcConstants.LauncherPidEnvVar}={Environment.ProcessId}");
|
|
arguments.Add($"--{LauncherIpcConstants.PackageRootEnvVar}={packageRoot}");
|
|
arguments.Add($"--{LauncherIpcConstants.VersionEnvVar}={versionInfo.Version}");
|
|
arguments.Add($"--{LauncherIpcConstants.CodenameEnvVar}={versionInfo.Codename}");
|
|
|
|
if (!string.IsNullOrWhiteSpace(dataRoot))
|
|
{
|
|
arguments.Add($"--{DataRootOptionName}={dataRoot}");
|
|
}
|
|
|
|
return arguments;
|
|
}
|
|
|
|
private static bool IsAppDeploymentDirectory(string path)
|
|
{
|
|
var fileName = Path.GetFileName(Path.TrimEndingDirectorySeparator(path));
|
|
return fileName.StartsWith("app-", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static bool IsParentOf(string parent, string child)
|
|
{
|
|
var parentPath = Path.GetFullPath(parent).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
var childPath = Path.GetFullPath(child).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
if (string.Equals(parentPath, childPath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return childPath.StartsWith(
|
|
parentPath + Path.DirectorySeparatorChar,
|
|
OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string QuoteArgument(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return "\"\"";
|
|
}
|
|
|
|
if (!value.Contains('"') && !value.Contains(' ') && !value.Contains('\t'))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var builder = new System.Text.StringBuilder();
|
|
builder.Append('"');
|
|
foreach (var ch in value)
|
|
{
|
|
if (ch == '"')
|
|
{
|
|
builder.Append("\\\"");
|
|
}
|
|
else
|
|
{
|
|
builder.Append(ch);
|
|
}
|
|
}
|
|
|
|
builder.Append('"');
|
|
return builder.ToString();
|
|
}
|
|
}
|