Skip to content

Enable 'partial' AOT of the CLI #48790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 24 additions & 4 deletions src/Cli/Microsoft.DotNet.Cli.Utils/DotnetFiles.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Microsoft.DotNet.Cli.Utils;

internal static class DotnetFiles
{
private static string SdkRootFolder => Path.Combine(typeof(DotnetFiles).GetTypeInfo().Assembly.Location, "..");
/// <summary>
/// Get the SDK root folder.
/// </summary>
/// <param name="sdkAssemblyPath">The path to any assembly that appears in the SDK root folder.</param>
private static string GetSdkRootFolder(string sdkAssemblyPath) => Path.Combine(sdkAssemblyPath, "..");

private static readonly Lazy<DotnetVersionFile> s_versionFileObject = new(
#if NET
[UnconditionalSuppressMessage("SingleFile", "IL3002", Justification = "All accesses are marked RAF")]
#endif
() => new DotnetVersionFile(VersionFile));

private static readonly Lazy<DotnetVersionFile> s_versionFileObject =
new(() => new DotnetVersionFile(VersionFile));
public static string GetVersionFilePath(string dotnetDllPath)
{
var sdkRootFolder = GetSdkRootFolder(dotnetDllPath);
return Path.GetFullPath(Path.Combine(sdkRootFolder, ".version"));
}

/// <summary>
/// The SDK ships with a .version file that stores the commit information and SDK version
/// </summary>
public static string VersionFile => Path.GetFullPath(Path.Combine(SdkRootFolder, ".version"));
#if NET
[RequiresAssemblyFiles]
#endif
public static string VersionFile => GetVersionFilePath(typeof(DotnetFiles).Assembly.Location);

#if NET
[RequiresAssemblyFiles]
#endif
internal static DotnetVersionFile VersionFileObject => s_versionFileObject.Value;
}
14 changes: 11 additions & 3 deletions src/Cli/Microsoft.DotNet.Cli.Utils/Product.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Microsoft.DotNet.Cli.Utils;
Expand All @@ -10,12 +11,19 @@ public class Product
public static string LongName => LocalizableStrings.DotNetSdkInfo;
public static readonly string Version = GetProductVersion();

#if NET
[RequiresAssemblyFiles]
#endif
private static string GetProductVersion()
{
DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
return GetProductVersion(typeof(Product).Assembly.Location);
}

public static string GetProductVersion(string dotnetDllPath)
{
DotnetVersionFile versionFile = new(DotnetFiles.GetVersionFilePath(dotnetDllPath));
return versionFile.BuildNumber ??
System.Diagnostics.FileVersionInfo.GetVersionInfo(
typeof(Product).GetTypeInfo().Assembly.Location)
System.Diagnostics.FileVersionInfo.GetVersionInfo(dotnetDllPath)
.ProductVersion ??
string.Empty;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

[assembly: AssemblyMetadataAttribute("Serviceable", "True")]
[assembly: InternalsVisibleTo("dotnet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Dotnet.Shared, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("dotnet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("dotnet-run.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Configurer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[assembly: AssemblyMetadataAttribute("Serviceable", "True")]
[assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyModel, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("dotnet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Dotnet.Shared, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Tests.ComponentMocks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Configurer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
Expand Down
81 changes: 81 additions & 0 deletions src/Cli/dotnet-aot/HostFxr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@

namespace Microsoft.DotNet.Cli;

internal unsafe readonly struct HostFxr
{
private readonly IntPtr _libHandle;
private readonly delegate*<
int, // argc
byte**, // argv
IntPtr, // hostfxr_initialize_parameters* parameters
IntPtr*, // out hostfxr_handle* host_context_handle
int> _initForCmdLine;
private readonly delegate*<IntPtr, int> _runApp;

private HostFxr(
IntPtr libHandle,
delegate*<int, byte**, IntPtr, IntPtr*, int> initForCmdLine,
delegate*<IntPtr, int> runApp)
{
_libHandle = libHandle;
_initForCmdLine = initForCmdLine;
_runApp = runApp;
}

public static HostFxr Load(string hostFxrPath)
{
if (string.IsNullOrEmpty(hostFxrPath))
{
throw new ArgumentException("Hostfxr path cannot be null or empty.", nameof(hostFxrPath));
}

if (Path.GetFileNameWithoutExtension(hostFxrPath) is not ("libhostfxr" or "hostfxr"))
{
throw new ArgumentException("The provided path is not a valid hostfxr file.", nameof(hostFxrPath));
}

var libHandle = NativeLibrary.Load(hostFxrPath);
var initForCmdLine = (delegate*<int, byte**, IntPtr, IntPtr*, int>)NativeLibrary.GetExport(libHandle, "hostfxr_initialize_for_dotnet_command_line");
var runApp = (delegate*<IntPtr, int>)NativeLibrary.GetExport(libHandle, "hostfxr_run_app");

return new HostFxr(libHandle, initForCmdLine, runApp);
}

public int Run(string dotnetPath, params string[] args)
{
string[] fullArgs = [dotnetPath, ..args];
if (TryInitForCmdLine(fullArgs, out var ctxHandle) != 0)
{
throw new InvalidOperationException("Failed to initialize hostfxr");
}
return _runApp(ctxHandle);
}

private int TryInitForCmdLine(string[] args, out IntPtr ctxHandle)
{
int argc = args.Length;
byte** argv = (byte**)Marshal.AllocHGlobal(argc * sizeof(byte*));
try
{
for (int i = 0; i < argc; i++)
{
var argPtr = (byte*)Marshal.StringToHGlobalAnsi(args[i]);
argv[i] = argPtr;
}

// Call the hostfxr function
IntPtr tmpHandle = IntPtr.Zero;
int rc = _initForCmdLine(argc, argv, IntPtr.Zero, &tmpHandle);
ctxHandle = tmpHandle;
return rc;
}
finally
{
for (int i = 0; i < argc; i++)
{
Marshal.FreeHGlobal((IntPtr)argv[i]);
}
Marshal.FreeHGlobal((IntPtr)argv);
}
}
}
109 changes: 109 additions & 0 deletions src/Cli/dotnet-aot/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Microsoft.DotNet.Cli.Extensions;

namespace Microsoft.DotNet.Cli;

public class Program
{
public static int Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: dotnet-aot <hostfxr_path> <dotnet_path> ...");
return 1;
}
string hostfxrPath = args[0];
string dotnetPath = args[1];

Console.WriteLine("Hostfxr path: " + hostfxrPath);
if (Path.GetFileNameWithoutExtension(hostfxrPath) is not ("libhostfxr" or "hostfxr"))
{
Console.WriteLine("Error: The provided path is not a valid hostfxr file.");
return 1;
}
if (!File.Exists(hostfxrPath))
{
Console.WriteLine($"Error: The provided path does not exist: {hostfxrPath}");
return 1;
}
Console.WriteLine("Dotnet path: " + dotnetPath);
if (Path.GetFileName(dotnetPath) is not "dotnet.dll")
{
Console.WriteLine("Error: The provided path is not a valid dotnet file.");
return 1;
}
if (!File.Exists(dotnetPath))
{
Console.WriteLine($"Error: The provided path does not exist: {dotnetPath}");
return 1;
}

return RunSdk(hostfxrPath, dotnetPath, args[2..]);
}

/// <summary>
/// Run the CLI SDK with the provided arguments.
/// </summary>
/// <param name="dotnetDllPathPtr">
/// Null-terminated absolute path to dotnet.dll. Wstring on Windows, UTF-8 on Unix.
/// </param>
/// <param name="sdkArgsPtr">Pointer to array of null-terminated command line args.</param>
/// <param name="sdkArgc">Length of command line arg array.</param>
[UnmanagedCallersOnly(EntryPoint = "run_sdk")]
public unsafe static int RunSdk(
IntPtr hostfxrPathPtr,
IntPtr dotnetDllPathPtr,
IntPtr* sdkArgsPtr,
int sdkArgc)
{
var hostfxrPath = Marshal.PtrToStringAuto(hostfxrPathPtr)
?? throw new ArgumentNullException(nameof(hostfxrPathPtr));
var dotnetDllPath = Marshal.PtrToStringAuto(dotnetDllPathPtr)
?? throw new ArgumentNullException(nameof(dotnetDllPathPtr));
var sdkArgs = new string[sdkArgc];
for (int i = 0; i < sdkArgc; i++)
{
var arg = Marshal.PtrToStringAuto(sdkArgsPtr[i])
?? throw new ArgumentNullException(nameof(sdkArgsPtr), $"Argument {i} is null");
sdkArgs[i] = arg;
}
return RunSdk(
hostfxrPath,
dotnetDllPath,
sdkArgs);
}

private static int RunSdk(
string hostfxrPath,
string dotnetDllPath,
string[] sdkArgs)
{
if (TryRunAotCli(dotnetDllPath, sdkArgs, out int exitCode))
{
// If these arguments were be handled by the AOT CLI, run directly. Otherwise
// bail out to load CoreCLR and run the full CLI.
return exitCode;
}

return LoadClrAndRun(hostfxrPath, dotnetDllPath, sdkArgs);
}

private static bool TryRunAotCli(string dotnetDllPath, string[] sdkArgs, out int exitCode)
{
// Only supported command right now is "--version"
if (sdkArgs is ["--version"])
{
CommandLineInfo.PrintVersion(dotnetDllPath);
exitCode = 0;
return true;
}
exitCode = 0;
return false;
}

public static int LoadClrAndRun(string hostfxrPath, string dotnetPath, string[] dotnetArgs)
{
// We need CoreCLR. Load it and run 'dotnet.dll'
var hostFxr = HostFxr.Load(hostfxrPath);
return hostFxr.Run(dotnetPath, dotnetArgs);
}
}
20 changes: 20 additions & 0 deletions src/Cli/dotnet-aot/dotnet-aot.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
<!-- Use lib prefix on non-Windows -->
<TargetName>dotnet_aot</TargetName>
<TargetName Condition="'$(OS)' != 'Windows_NT'">lib$(TargetName)</TargetName>
<UseCurrentRuntimeIdentifier>true</UseCurrentRuntimeIdentifier>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../dotnet-shared/dotnet-shared.csproj" />
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions src/Cli/dotnet-shared/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("dotnet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public static void PrintVersion()
Reporter.Output.WriteLine(Product.Version);
}

public static void PrintVersion(string dotnetDllPath)
{
Reporter.Output.WriteLine(Product.GetProductVersion(dotnetDllPath));
}

public static void PrintInfo()
{
DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
Expand Down
Loading
Loading