Skip to content

Commit

Permalink
Fixes #3109. AOT support with .Net 8. (#3638)
Browse files Browse the repository at this point in the history
* Add a native AOT project.

* Fixes Text.Json to work with native AOT.

* Fix silent errors on unit tests when testing the Red color which has a length of 3.

* Allowing test custom configuration without the config.json file match the unit tests configurations.

* Fix unit test if tested alone.

* Add native project into solution.

* Fix merge errors.

* Setting ConfigurationManager.ThrowOnJsonErrors as true to throw any serialization issue when published file runs.

* Remove unnecessary using's.

* Added unit test to ensure all serialization is properly configured.

* Fix warnings.

* Remove ThrowOnJsonErrors.

* Fix warnings.

---------

Co-authored-by: Tig <[email protected]>
  • Loading branch information
BDisp and tig authored Aug 6, 2024
1 parent 1b973ee commit 63e75b7
Show file tree
Hide file tree
Showing 27 changed files with 443 additions and 82 deletions.
22 changes: 22 additions & 0 deletions NativeAot/NativeAot.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>false</InvariantGlobalization>
</PropertyGroup>

<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
<TrimmerRootAssembly Include="Terminal.Gui" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PackageReference Include="Terminal.Gui" Version="[2.0.0-pre.1788,3)" />
<TrimmerRootAssembly Include="Terminal.Gui" />
</ItemGroup>

</Project>
113 changes: 113 additions & 0 deletions NativeAot/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// This is a test application for a native Aot file.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Terminal.Gui;

namespace NativeAot;

public static class Program
{
[RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
[RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
private static void Main (string [] args)
{
Application.Init ();

#region The code in this region is not intended for use in a native Aot self-contained. It's just here to make sure there is no functionality break with localization in Terminal.Gui using self-contained

if (Equals(Thread.CurrentThread.CurrentUICulture, CultureInfo.InvariantCulture) && Application.SupportedCultures.Count == 0)
{
// Only happens if the project has <InvariantGlobalization>true</InvariantGlobalization>
Debug.Assert (Application.SupportedCultures.Count == 0);
}
else
{
Debug.Assert (Application.SupportedCultures.Count > 0);
Debug.Assert (Equals (CultureInfo.CurrentCulture, Thread.CurrentThread.CurrentUICulture));
}

#endregion

ExampleWindow app = new ();
Application.Run (app);

// Dispose the app object before shutdown
app.Dispose ();

// Before the application exits, reset Terminal.Gui for clean shutdown
Application.Shutdown ();

// To see this output on the screen it must be done after shutdown,
// which restores the previous screen.
Console.WriteLine ($@"Username: {ExampleWindow.UserName}");
}
}

// Defines a top-level window with border and title
public class ExampleWindow : Window
{
public static string? UserName;

public ExampleWindow ()
{
Title = $"Example App ({Application.QuitKey} to quit)";

// Create input components and labels
var usernameLabel = new Label { Text = "Username:" };

var userNameText = new TextField
{
// Position text field adjacent to the label
X = Pos.Right (usernameLabel) + 1,

// Fill remaining horizontal space
Width = Dim.Fill ()
};

var passwordLabel = new Label
{
Text = "Password:", X = Pos.Left (usernameLabel), Y = Pos.Bottom (usernameLabel) + 1
};

var passwordText = new TextField
{
Secret = true,

// align with the text box above
X = Pos.Left (userNameText),
Y = Pos.Top (passwordLabel),
Width = Dim.Fill ()
};

// Create login button
var btnLogin = new Button
{
Text = "Login",
Y = Pos.Bottom (passwordLabel) + 1,

// center the login button horizontally
X = Pos.Center (),
IsDefault = true
};

// When login button is clicked display a message popup
btnLogin.Accept += (s, e) =>
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
MessageBox.Query ("Logging In", "Login Successful", "Ok");
UserName = userNameText.Text;
Application.RequestStop ();
}
else
{
MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
}
};

// Add the views to the Window
Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Debug</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Debug\net8.0\publish\win-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>
5 changes: 5 additions & 0 deletions NativeAot/Publish_linux-x64_Debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

dotnet clean
dotnet build
dotnet publish -c Debug -r linux-x64 --self-contained
5 changes: 5 additions & 0 deletions NativeAot/Publish_linux-x64_Release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

dotnet clean
dotnet build
dotnet publish -c Release -r linux-x64 --self-contained
5 changes: 5 additions & 0 deletions NativeAot/Publish_osx-x64_Debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

dotnet clean
dotnet build
dotnet publish -c Debug -r osx-x64 --self-contained
5 changes: 5 additions & 0 deletions NativeAot/Publish_osx-x64_Release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

dotnet clean
dotnet build
dotnet publish -c Release -r osx-x64 --self-contained
10 changes: 10 additions & 0 deletions NativeAot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Terminal.Gui C# SelfContained

This project aims to test the `Terminal.Gui` library to create a simple `native AOT` `self-container` GUI application in C#, ensuring that all its features are available.

With `Debug` the `.csproj` is used and with `Release` the latest `nuget package` is used, either in `Solution Configurations` or in `Profile Publish` or in the `Publish_linux-x64` or in the `Publish_osx-x64` files.
Unlike self-contained single-file publishing, native AOT publishing must be generated on the same platform as the target execution version. Therefore, if the target execution is Linux, then the publishing must be generated on a Linux operating system. Attempting to generate on Windows for the Linux target will throw an exception.

To publish the `native AOT` file in `Debug` or `Release` mode, it is not necessary to select it in the `Solution Configurations`, just choose the `Debug` or `Release` configuration in the `Publish Profile` or the `*.sh` files.

When executing the file directly from the `native AOT` file and needing to debug it, it will be necessary to attach it to the debugger, just like any other standalone application and selecting `Native Code`.
12 changes: 6 additions & 6 deletions Terminal.Gui/Configuration/SourceGenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ namespace Terminal.Gui;
/// </summary>
[JsonSerializable (typeof (Attribute))]
[JsonSerializable (typeof (Color))]
[JsonSerializable (typeof (ThemeScope))]
[JsonSerializable (typeof (ColorScheme))]
[JsonSerializable (typeof (SettingsScope))]
[JsonSerializable (typeof (AppScope))]
[JsonSerializable (typeof (SettingsScope))]
[JsonSerializable (typeof (Key))]
[JsonSerializable (typeof (GlyphDefinitions))]
[JsonSerializable (typeof (ConfigProperty))]
[JsonSerializable (typeof (Alignment))]
[JsonSerializable (typeof (AlignmentModes))]
[JsonSerializable (typeof (LineStyle))]
[JsonSerializable (typeof (ShadowStyle))]
[JsonSerializable (typeof (string))]
[JsonSerializable (typeof (bool))]
[JsonSerializable (typeof (bool?))]
[JsonSerializable (typeof (Dictionary<ColorName, string>))]
[JsonSerializable (typeof (Dictionary<string, ThemeScope>))]
[JsonSerializable (typeof (Dictionary<string, ColorScheme>))]
internal partial class SourceGenerationContext : JsonSerializerContext
{ }
3 changes: 2 additions & 1 deletion Terminal.Gui/Drawing/Alignment.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

using System.Text.Json.Serialization;

namespace Terminal.Gui;

/// <summary>
/// Determines the position of items when arranged in a container.
/// </summary>
[JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
public enum Alignment
{
/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion Terminal.Gui/Drawing/AlignmentModes.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

using System.Text.Json.Serialization;

namespace Terminal.Gui;

/// <summary>
/// Determines alignment modes for <see cref="Alignment"/>.
/// </summary>
[JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
[Flags]
public enum AlignmentModes
{
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Drawing/Color.Formatting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ in text
),

// Any string too short to possibly be any supported format.
{ Length: > 0 and < 4 } => throw new ColorParseException (
{ Length: > 0 and < 3 } => throw new ColorParseException (
in text,
"Text was too short to be any possible supported format.",
in text
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Drawing/LineStyle.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#nullable enable
using System.Text.Json.Serialization;

namespace Terminal.Gui;

/// <summary>Defines the style of lines for a <see cref="LineCanvas"/>.</summary>
[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
public enum LineStyle
{
/// <summary>No border is drawn.</summary>
Expand Down
5 changes: 4 additions & 1 deletion Terminal.Gui/View/Adornment/ShadowStyle.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Terminal.Gui;
using System.Text.Json.Serialization;

namespace Terminal.Gui;

/// <summary>
/// Defines the style of shadow to be drawn on the right and bottom sides of the <see cref="View"/>.
/// </summary>
[JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
public enum ShadowStyle
{
/// <summary>
Expand Down
4 changes: 0 additions & 4 deletions Terminal.Gui/Views/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
// Miguel de Icaza ([email protected])
//

using System.Text.Json.Serialization;

namespace Terminal.Gui;

/// <summary>Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="View.Accept"/> event.</summary>
Expand Down Expand Up @@ -39,8 +37,6 @@ public class Button : View, IDesignable
/// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]

public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;

/// <summary>Initializes a new instance of <see cref="Button"/>.</summary>
Expand Down
10 changes: 2 additions & 8 deletions Terminal.Gui/Views/Dialog.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Text.Json.Serialization;

namespace Terminal.Gui;
namespace Terminal.Gui;

/// <summary>
/// The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains
Expand All @@ -19,13 +17,11 @@ public class Dialog : Window
/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
/// <remarks>This property can be set in a Theme.</remarks>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End; // Default is set in config.json

/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
/// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
/// <remarks>This property can be set in a Theme.</remarks>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;

/// <summary>
Expand All @@ -47,7 +43,6 @@ public class Dialog : Window
/// Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None; // Default is set in config.json

/// <summary>
Expand All @@ -56,7 +51,6 @@ public class Dialog : Window
/// </summary>

[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json

private readonly List<Button> _buttons = new ();
Expand Down
5 changes: 1 addition & 4 deletions Terminal.Gui/Views/FrameView.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Text.Json.Serialization;

namespace Terminal.Gui;
namespace Terminal.Gui;

/// <summary>
/// The FrameView is a container View with a border around it.
Expand Down Expand Up @@ -38,6 +36,5 @@ private void FrameView_MouseClick (object sender, MouseEventEventArgs e)
/// <see cref="FrameView"/>s.
/// </remarks>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
}
7 changes: 1 addition & 6 deletions Terminal.Gui/Views/MessageBox.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Diagnostics;
using System.Text.Json.Serialization;

namespace Terminal.Gui;
namespace Terminal.Gui;

/// <summary>
/// MessageBox displays a modal message to the user, with a title, a message and a series of options that the user
Expand Down Expand Up @@ -32,13 +29,11 @@ public static class MessageBox
/// <see cref="ConfigurationManager"/>.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json

/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
/// <remarks>This property can be set in a Theme.</remarks>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
[JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
public static Alignment DefaultButtonAlignment { get; set; } = Alignment.Center; // Default is set in config.json

/// <summary>
Expand Down
Loading

0 comments on commit 63e75b7

Please sign in to comment.