Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Assets/Tests/Editor/NativeCliInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class NativeCliInstallerTests
public void GetInstallCommand_OnMacKeepsDispatcherCurlInstallerAvailable()
{
// Verifies that editor installs use the dispatcher installer script, not npm.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.OSXEditor,
TestBetaCliVersion,
false,
Expand All @@ -44,7 +44,7 @@ public void GetInstallCommand_OnMacKeepsDispatcherCurlInstallerAvailable()
public void GetInstallCommand_OnMacDelegatesPosixSnippetThroughSh()
{
// Verifies that fish or other login shells only load environment before POSIX script execution.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.OSXEditor,
TestBetaCliVersion,
false,
Expand All @@ -61,7 +61,7 @@ public void GetInstallCommand_OnMacDelegatesPosixSnippetThroughSh()
public void GetInstallCommand_OnMacPropagatesInstallerDownloadFailure()
{
// Verifies that editor installs do not report success when curl fails before script execution.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.OSXEditor,
TestBetaCliVersion,
false,
Expand All @@ -77,7 +77,7 @@ public void GetInstallCommand_OnMacPropagatesInstallerDownloadFailure()
public void GetInstallCommand_OnWindowsKeepsDispatcherPowerShellInstallerAvailable()
{
// Verifies that editor installs use the dispatcher PowerShell installer script.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.WindowsEditor,
TestBetaCliVersion,
false,
Expand All @@ -95,7 +95,7 @@ public void GetInstallCommand_OnWindowsKeepsDispatcherPowerShellInstallerAvailab
public void GetInstallCommand_OnMacDispatcherInstallerDoesNotAdvertiseWindowsLegacyCleanup()
{
// Verifies that macOS manual commands do not expose old cleanup flags.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.OSXEditor,
TestBetaCliVersion,
true,
Expand All @@ -109,7 +109,7 @@ public void GetInstallCommand_OnMacDispatcherInstallerDoesNotAdvertiseWindowsLeg
public void GetInstallCommand_OnWindowsDoesNotNeedLegacyCleanupFlag()
{
// Verifies that Windows installs rely on the native CLI install command for legacy cleanup.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.WindowsEditor,
TestBetaCliVersion,
true,
Expand All @@ -123,7 +123,7 @@ public void GetInstallCommand_OnWindowsDoesNotNeedLegacyCleanupFlag()
public void GetInstallCommand_WhenVPrefixedVersionUsesDispatcherReleaseTag()
{
// Verifies root release tags are normalized to dispatcher releases for installer scripts.
NativeCliInstallCommand command = NativeCliInstaller.BuildRemoteInstallCommand(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildRemoteInstallCommand(
RuntimePlatform.OSXEditor,
"v3.0.0",
false,
Expand Down Expand Up @@ -156,7 +156,7 @@ public void GetInstallCommand_WhenLocalPackageOnMacUsesPackageLocalInstaller()

try
{
NativeCliInstallCommand command = NativeCliInstaller.BuildInstallCommandWithPackagePath(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildInstallCommandWithPackagePath(
RuntimePlatform.OSXEditor,
TestBetaCliVersion,
false,
Expand Down Expand Up @@ -202,7 +202,7 @@ public void GetInstallCommand_WhenLocalPackageOnWindowsUsesPackageLocalInstaller

try
{
NativeCliInstallCommand command = NativeCliInstaller.BuildInstallCommandWithPackagePath(
NativeCliInstallCommand command = NativeCliCommandBuilder.BuildInstallCommandWithPackagePath(
RuntimePlatform.WindowsEditor,
TestBetaCliVersion,
false,
Expand All @@ -228,7 +228,7 @@ public void GetInstallCommand_WhenLocalPackageOnWindowsUsesPackageLocalInstaller
public void BuildInstallerScriptUrl_WhenBetaVersionUsesReleaseInstallerScript()
{
// Verifies that beta editor installs use the script shipped with the selected dispatcher release.
string url = NativeCliInstaller.BuildInstallerScriptUrl(
string url = NativeCliCommandBuilder.BuildInstallerScriptUrl(
TestBetaReleaseTag,
CliConstants.POSIX_INSTALL_SCRIPT_NAME);

Expand All @@ -239,7 +239,7 @@ public void BuildInstallerScriptUrl_WhenBetaVersionUsesReleaseInstallerScript()
public void BuildInstallerScriptUrl_WhenStableVersionUsesReleaseInstallerScript()
{
// Verifies that stable editor installs use the script shipped with the selected dispatcher release.
string url = NativeCliInstaller.BuildInstallerScriptUrl(
string url = NativeCliCommandBuilder.BuildInstallerScriptUrl(
TestStableReleaseTag,
CliConstants.WINDOWS_INSTALL_SCRIPT_NAME);

Expand Down
2 changes: 1 addition & 1 deletion Assets/Tests/Editor/StaticFacadeStateGuardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public sealed class StaticFacadeStateGuardTests
new[] { "TryCompileAsync" }
},
{
"Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs",
"Packages/src/Editor/Infrastructure/CLI/NativeCliCommandBuilder.cs",
new[] { "BuildRemoteInstallCommand", "BuildInstallCommandWithPackagePath" }
},
{
Expand Down
189 changes: 189 additions & 0 deletions Packages/src/Editor/Infrastructure/CLI/NativeCliCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using System;
using System.IO;

using UnityEngine;

using io.github.hatayama.UnityCliLoop.Application;
using io.github.hatayama.UnityCliLoop.Domain;

namespace io.github.hatayama.UnityCliLoop.Infrastructure
{
/// <summary>
/// Builds native CLI installer commands from platform and package inputs.
/// </summary>
internal static class NativeCliCommandBuilder
{
internal static NativeCliInstallCommand BuildRemoteInstallCommand(
RuntimePlatform platform,
string cliReleaseTag,
bool removeLegacyLaunchers,
string posixShellPath)
{
return BuildInstallCommandWithPackagePath(
platform,
cliReleaseTag,
removeLegacyLaunchers,
posixShellPath,
null);
}

internal static NativeCliInstallCommand BuildInstallCommandWithPackagePath(
RuntimePlatform platform,
string cliReleaseTag,
bool removeLegacyLaunchers,
string posixShellPath,
string packageResolvedPath)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(cliReleaseTag), "cliReleaseTag must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(posixShellPath), "posixShellPath must not be null or empty");
_ = removeLegacyLaunchers;

string releaseTag = BuildReleaseTag(cliReleaseTag);
if (platform == RuntimePlatform.WindowsEditor)
{
string localScriptPath = ResolvePackageLocalInstallerScriptPath(
packageResolvedPath,
CliConstants.WINDOWS_INSTALL_SCRIPT_NAME);
string command = string.IsNullOrEmpty(localScriptPath)
? BuildWindowsRemoteInstallScriptCommand(
BuildInstallerScriptUrl(releaseTag, CliConstants.WINDOWS_INSTALL_SCRIPT_NAME),
releaseTag)
: BuildWindowsLocalInstallScriptCommand(localScriptPath, releaseTag);
return new NativeCliInstallCommand(
"powershell",
$"-NoProfile -ExecutionPolicy Bypass -Command \"{command}\"",
command);
}

string posixLocalScriptPath = ResolvePackageLocalInstallerScriptPath(
packageResolvedPath,
CliConstants.POSIX_INSTALL_SCRIPT_NAME);
string posixCommand = string.IsNullOrEmpty(posixLocalScriptPath)
? BuildPosixRemoteInstallScriptCommand(
BuildInstallerScriptUrl(releaseTag, CliConstants.POSIX_INSTALL_SCRIPT_NAME),
releaseTag)
: BuildPosixLocalInstallScriptCommand(posixLocalScriptPath, releaseTag);
string loginShellCommand = BuildLoginShellPosixInstallScriptCommand(posixCommand);
return new NativeCliInstallCommand(
posixShellPath,
$"-l -i -c {QuoteProcessArgument(loginShellCommand)}",
loginShellCommand);
}

private static string BuildPosixRemoteInstallScriptCommand(string scriptUrl, string releaseTag)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(scriptUrl), "scriptUrl must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(releaseTag), "releaseTag must not be null or empty");

return "tmp_script=$(mktemp) && "
+ "trap 'rm -f \"$tmp_script\"' EXIT && "
+ $"curl -fsSL {QuotePosixShellValue(scriptUrl)} -o \"$tmp_script\" && "
+ $"{CliConstants.INSTALL_VERSION_ENVIRONMENT_VARIABLE}={QuotePosixShellValue(releaseTag)} sh \"$tmp_script\"";
}

private static string BuildPosixLocalInstallScriptCommand(string scriptPath, string releaseTag)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(scriptPath), "scriptPath must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(releaseTag), "releaseTag must not be null or empty");

return $"{CliConstants.INSTALL_VERSION_ENVIRONMENT_VARIABLE}={QuotePosixShellValue(releaseTag)} sh {QuotePosixShellValue(scriptPath)}";
}

internal static string BuildLoginShellPosixInstallScriptCommand(string posixCommand)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(posixCommand), "posixCommand must not be null or empty");
return $"{CliConstants.POSIX_SHELL_EXECUTABLE_PATH} -c {QuotePosixShellValue(posixCommand)}";
}

private static string BuildWindowsRemoteInstallScriptCommand(string scriptUrl, string releaseTag)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(scriptUrl), "scriptUrl must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(releaseTag), "releaseTag must not be null or empty");

return $"$env:{CliConstants.INSTALL_VERSION_ENVIRONMENT_VARIABLE}={QuotePowerShellSingleQuotedValue(releaseTag)}; "
+ $"irm {QuotePowerShellSingleQuotedValue(scriptUrl)} | iex";
}

private static string BuildWindowsLocalInstallScriptCommand(string scriptPath, string releaseTag)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(scriptPath), "scriptPath must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(releaseTag), "releaseTag must not be null or empty");

return $"$env:{CliConstants.INSTALL_VERSION_ENVIRONMENT_VARIABLE}={QuotePowerShellSingleQuotedValue(releaseTag)}; "
+ $"& {QuotePowerShellSingleQuotedValue(scriptPath)}";
}

internal static string ResolvePackageLocalInstallerScriptPath(string packageResolvedPath, string assetName)
{
if (string.IsNullOrWhiteSpace(packageResolvedPath) || string.IsNullOrWhiteSpace(assetName))
{
return null;
}

DirectoryInfo packageDirectory = new(packageResolvedPath);
DirectoryInfo packagesDirectory = packageDirectory.Parent;
DirectoryInfo repositoryDirectory = packagesDirectory?.Parent;
if (repositoryDirectory == null)
{
return null;
}

if (!string.Equals(packageDirectory.Name, CliConstants.PACKAGE_SOURCE_DIR_NAME, StringComparison.Ordinal)
|| !string.Equals(packagesDirectory.Name, CliConstants.UNITY_PACKAGES_DIR_NAME, StringComparison.Ordinal))
{
return null;
}

string scriptPath = Path.Combine(repositoryDirectory.FullName, CliConstants.SCRIPTS_DIR_NAME, assetName);
if (!File.Exists(scriptPath))
{
return null;
}

return scriptPath;
}

private static string QuotePosixShellValue(string value)
{
UnityEngine.Debug.Assert(value != null, "value must not be null");
return $"'{value.Replace("'", "'\"'\"'")}'";
}

private static string QuotePowerShellSingleQuotedValue(string value)
{
UnityEngine.Debug.Assert(value != null, "value must not be null");
return $"'{value.Replace("'", "''")}'";
}

internal static string QuoteProcessArgument(string value)
{
UnityEngine.Debug.Assert(value != null, "value must not be null");
return $"\"{value.Replace("\"", "\\\"")}\"";
}

private static string BuildReleaseTag(string cliReleaseTag)
{
if (cliReleaseTag.StartsWith(CliConstants.DISPATCHER_RELEASE_TAG_PREFIX, StringComparison.Ordinal))
{
return cliReleaseTag;
}
if (cliReleaseTag.StartsWith(CliConstants.PROJECT_RUNNER_RELEASE_TAG_PREFIX, StringComparison.Ordinal))
{
return $"{CliConstants.DISPATCHER_RELEASE_TAG_PREFIX}{cliReleaseTag.Substring(CliConstants.PROJECT_RUNNER_RELEASE_TAG_PREFIX.Length)}";
}
if (cliReleaseTag.StartsWith(CliConstants.RELEASE_TAG_PREFIX, StringComparison.Ordinal))
{
return $"{CliConstants.DISPATCHER_RELEASE_TAG_PREFIX}{cliReleaseTag.Substring(CliConstants.RELEASE_TAG_PREFIX.Length)}";
}
return $"{CliConstants.DISPATCHER_RELEASE_TAG_PREFIX}{cliReleaseTag}";
}

internal static string BuildInstallerScriptUrl(string releaseTag, string assetName)
{
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(releaseTag), "releaseTag must not be null or empty");
UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(assetName), "assetName must not be null or empty");

return $"{CliConstants.RAW_CONTENT_BASE_URL}/{releaseTag}/{CliConstants.SCRIPTS_DIR_NAME}/{assetName}";
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading