feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -7,7 +7,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.NET.Sdk</PackageId>
<Version>4.3.0</Version>
<Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
@ -30,6 +30,7 @@
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
<None Include="Sdk\Android.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
</ItemGroup>

View file

@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' and '$(PublishAot)' != 'true' ">true</UseMonoRuntime>
</PropertyGroup>
</Project>

View file

@ -112,5 +112,6 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\Android.props" Condition=" '$(GodotTargetPlatform)' == 'android' " />
<Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
</Project>

View file

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
</PropertyGroup>
<PropertyGroup>

View file

@ -21,7 +21,7 @@ public static class CSharpAnalyzerVerifier<TAnalyzer>
{
public Test()
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
ReferenceAssemblies = Constants.Net80;
SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
{

View file

@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
namespace Godot.SourceGenerators.Tests;
@ -17,7 +16,7 @@ public static class CSharpCodeFixVerifier<TCodeFix, TAnalyzer>
{
public Test()
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
ReferenceAssemblies = Constants.Net80;
SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
{
Project project = solution.GetProject(projectId)!

View file

@ -18,7 +18,7 @@ where TSourceGenerator : ISourceGenerator, new()
{
public Test()
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
ReferenceAssemblies = Constants.Net80;
SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
{

View file

@ -1,5 +1,6 @@
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis.Testing;
namespace Godot.SourceGenerators.Tests;
@ -7,6 +8,13 @@ public static class Constants
{
public static Assembly GodotSharpAssembly => typeof(GodotObject).Assembly;
// Can't find what needs updating to be able to access ReferenceAssemblies.Net.Net80, so we're making our own one.
public static ReferenceAssemblies Net80 => new ReferenceAssemblies(
"net8.0",
new PackageIdentity("Microsoft.NETCore.App.Ref", "8.0.0"),
Path.Combine("ref", "net8.0")
);
public static string ExecutingAssemblyPath { get; }
public static string SourceFolderPath { get; }
public static string GeneratedSourceFolderPath { get; }

View file

@ -74,4 +74,40 @@ public class ExportDiagnosticsTests
}
);
}
[Fact]
public async void ExportToolButtonInNonToolClass()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportDiagnostics_GD0108.cs" },
new string[] { "ExportDiagnostics_GD0108_ScriptProperties.generated.cs" }
);
}
[Fact]
public async void ExportAndExportToolButtonOnSameMember()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportDiagnostics_GD0109.cs" },
new string[] { "ExportDiagnostics_GD0109_ScriptProperties.generated.cs" }
);
}
[Fact]
public async void ExportToolButtonOnNonCallable()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportDiagnostics_GD0110.cs" },
new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" }
);
}
[Fact]
public async void ExportToolButtonStoringCallable()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "ExportDiagnostics_GD0111.cs" },
new string[] { "ExportDiagnostics_GD0111_ScriptProperties.generated.cs" }
);
}
}

View file

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

View file

@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests
"AbstractGenericNode(Of T)_ScriptProperties.generated.cs"
);
}
[Fact]
public async void ExportedButtons()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
"ExportedToolButtons.cs",
"ExportedToolButtons_ScriptProperties.generated.cs"
);
}
}

View file

@ -32,6 +32,10 @@ partial class EventSignals
add => backing_MySignal += value;
remove => backing_MySignal -= value;
}
protected void EmitSignalMySignal(string @str, int @num)
{
EmitSignal(SignalName.MySignal, @str, @num);
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args)

View file

@ -11,11 +11,27 @@ partial class ExportDiagnostics_GD0107_OK
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues()
{
var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(2);
var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(10);
global::Godot.Node __NodeProperty_default_value = default;
values.Add(PropertyName.@NodeProperty, global::Godot.Variant.From<global::Godot.Node>(__NodeProperty_default_value));
global::Godot.Node[] __SystemArrayOfNodesProperty_default_value = default;
values.Add(PropertyName.@SystemArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesProperty_default_value));
global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesProperty_default_value = default;
values.Add(PropertyName.@GodotArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesProperty_default_value));
global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyProperty_default_value = default;
values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyProperty_default_value));
global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueProperty_default_value = default;
values.Add(PropertyName.@GodotDictionaryWithNodeAsValueProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueProperty_default_value));
global::Godot.Node __NodeField_default_value = default;
values.Add(PropertyName.@NodeField, global::Godot.Variant.From<global::Godot.Node>(__NodeField_default_value));
global::Godot.Node[] __SystemArrayOfNodesField_default_value = default;
values.Add(PropertyName.@SystemArrayOfNodesField, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesField_default_value));
global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesField_default_value = default;
values.Add(PropertyName.@GodotArrayOfNodesField, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesField_default_value));
global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyField_default_value = default;
values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyField_default_value));
global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueField_default_value = default;
values.Add(PropertyName.@GodotDictionaryWithNodeAsValueField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueField_default_value));
return values;
}
#endif // TOOLS

View file

@ -0,0 +1,38 @@
using Godot;
using Godot.NativeInterop;
partial class ExportDiagnostics_GD0108
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButton = "MyButton";
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
if (name == PropertyName.@MyButton) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// Get the property information for all the properties declared in this class.
/// This method is used by Godot to register the available properties in the editor.
/// Do not call this method.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
return properties;
}
#pragma warning restore CS0109
}

View file

@ -0,0 +1,38 @@
using Godot;
using Godot.NativeInterop;
partial class ExportDiagnostics_GD0109
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButton = "MyButton";
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
if (name == PropertyName.@MyButton) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// Get the property information for all the properties declared in this class.
/// This method is used by Godot to register the available properties in the editor.
/// Do not call this method.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
return properties;
}
#pragma warning restore CS0109
}

View file

@ -0,0 +1,38 @@
using Godot;
using Godot.NativeInterop;
partial class ExportDiagnostics_GD0110
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButton' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButton = "MyButton";
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
if (name == PropertyName.@MyButton) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@MyButton);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// Get the property information for all the properties declared in this class.
/// This method is used by Godot to register the available properties in the editor.
/// Do not call this method.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
return properties;
}
#pragma warning restore CS0109
}

View file

@ -0,0 +1,116 @@
using Godot;
using Godot.NativeInterop;
partial class ExportDiagnostics_GD0111
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyButtonGet' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGet = "MyButtonGet";
/// <summary>
/// Cached name for the 'MyButtonGetSet' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetSet = "MyButtonGetSet";
/// <summary>
/// Cached name for the 'MyButtonGetWithBackingField' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetWithBackingField = "MyButtonGetWithBackingField";
/// <summary>
/// Cached name for the 'MyButtonGetSetWithBackingField' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonGetSetWithBackingField = "MyButtonGetSetWithBackingField";
/// <summary>
/// Cached name for the 'MyButtonOkWithCallableCreationExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithCallableCreationExpression = "MyButtonOkWithCallableCreationExpression";
/// <summary>
/// Cached name for the 'MyButtonOkWithImplicitCallableCreationExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithImplicitCallableCreationExpression = "MyButtonOkWithImplicitCallableCreationExpression";
/// <summary>
/// Cached name for the 'MyButtonOkWithCallableFromExpression' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButtonOkWithCallableFromExpression = "MyButtonOkWithCallableFromExpression";
/// <summary>
/// Cached name for the '_backingField' field.
/// </summary>
public new static readonly global::Godot.StringName @_backingField = "_backingField";
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
{
if (name == PropertyName.@MyButtonGetSet) {
this.@MyButtonGetSet = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
return true;
}
if (name == PropertyName.@MyButtonGetSetWithBackingField) {
this.@MyButtonGetSetWithBackingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
return true;
}
if (name == PropertyName.@_backingField) {
this.@_backingField = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value);
return true;
}
return base.SetGodotClassPropertyValue(name, value);
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
if (name == PropertyName.@MyButtonGet) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGet);
return true;
}
if (name == PropertyName.@MyButtonGetSet) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetSet);
return true;
}
if (name == PropertyName.@MyButtonGetWithBackingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetWithBackingField);
return true;
}
if (name == PropertyName.@MyButtonGetSetWithBackingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonGetSetWithBackingField);
return true;
}
if (name == PropertyName.@MyButtonOkWithCallableCreationExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithCallableCreationExpression);
return true;
}
if (name == PropertyName.@MyButtonOkWithImplicitCallableCreationExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithImplicitCallableCreationExpression);
return true;
}
if (name == PropertyName.@MyButtonOkWithCallableFromExpression) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButtonOkWithCallableFromExpression);
return true;
}
if (name == PropertyName.@_backingField) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@_backingField);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// Get the property information for all the properties declared in this class.
/// This method is used by Godot to register the available properties in the editor.
/// Do not call this method.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@_backingField, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithImplicitCallableCreationExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButtonOkWithCallableFromExpression, hint: (global::Godot.PropertyHint)39, hintString: "", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
return properties;
}
#pragma warning restore CS0109
}

View file

@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;

View file

@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}

View file

@ -0,0 +1,48 @@
using Godot;
using Godot.NativeInterop;
partial class ExportedToolButtons
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.GodotObject.PropertyName {
/// <summary>
/// Cached name for the 'MyButton1' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButton1 = "MyButton1";
/// <summary>
/// Cached name for the 'MyButton2' property.
/// </summary>
public new static readonly global::Godot.StringName @MyButton2 = "MyButton2";
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
if (name == PropertyName.@MyButton1) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton1);
return true;
}
if (name == PropertyName.@MyButton2) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton2);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// Get the property information for all the properties declared in this class.
/// This method is used by Godot to register the available properties in the editor.
/// Do not call this method.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton1, hint: (global::Godot.PropertyHint)39, hintString: "Click me!", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton2, hint: (global::Godot.PropertyHint)39, hintString: "Click me!,ColorRect", usage: (global::Godot.PropertyUsageFlags)4, exported: true));
return properties;
}
#pragma warning restore CS0109
}

View file

@ -1,12 +1,37 @@
using Godot;
using Godot.Collections;
public partial class ExportDiagnostics_GD0107_OK : Node
{
[Export]
public Node NodeField;
[Export]
public Node[] SystemArrayOfNodesField;
[Export]
public Array<Node> GodotArrayOfNodesField;
[Export]
public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyField;
[Export]
public Dictionary<string, Node> GodotDictionaryWithNodeAsValueField;
[Export]
public Node NodeProperty { get; set; }
[Export]
public Node[] SystemArrayOfNodesProperty { get; set; }
[Export]
public Array<Node> GodotArrayOfNodesProperty { get; set; }
[Export]
public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyProperty { get; set; }
[Export]
public Dictionary<string, Node> GodotDictionaryWithNodeAsValueProperty { get; set; }
}
public partial class ExportDiagnostics_GD0107_KO : Resource
@ -14,6 +39,30 @@ public partial class ExportDiagnostics_GD0107_KO : Resource
[Export]
public Node {|GD0107:NodeField|};
[Export]
public Node[] {|GD0107:SystemArrayOfNodesField|};
[Export]
public Array<Node> {|GD0107:GodotArrayOfNodesField|};
[Export]
public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyField|};
[Export]
public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueField|};
[Export]
public Node {|GD0107:NodeProperty|} { get; set; }
[Export]
public Node[] {|GD0107:SystemArrayOfNodesProperty|} { get; set; }
[Export]
public Array<Node> {|GD0107:GodotArrayOfNodesProperty|} { get; set; }
[Export]
public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyProperty|} { get; set; }
[Export]
public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueProperty|} { get; set; }
}

View file

@ -0,0 +1,8 @@
using Godot;
using Godot.Collections;
public partial class ExportDiagnostics_GD0108 : Node
{
[ExportToolButton("")]
public Callable {|GD0108:MyButton|} => new Callable();
}

View file

@ -0,0 +1,9 @@
using Godot;
using Godot.Collections;
[Tool]
public partial class ExportDiagnostics_GD0109 : Node
{
[Export, ExportToolButton("")]
public Callable {|GD0109:MyButton|} => new Callable();
}

View file

@ -0,0 +1,9 @@
using Godot;
using Godot.Collections;
[Tool]
public partial class ExportDiagnostics_GD0110 : Node
{
[ExportToolButton("")]
public int {|GD0110:MyButton|} => new();
}

View file

@ -0,0 +1,29 @@
using Godot;
using Godot.Collections;
[Tool]
public partial class ExportDiagnostics_GD0111 : Node
{
private Callable _backingField;
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGet|} { get; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetSet|} { get; set; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetWithBackingField|} { get => _backingField; }
[ExportToolButton("")]
public Callable {|GD0111:MyButtonGetSetWithBackingField|} { get => _backingField; set => _backingField = value; }
[ExportToolButton("")]
public Callable MyButtonOkWithCallableCreationExpression => new Callable(this, "");
[ExportToolButton("")]
public Callable MyButtonOkWithImplicitCallableCreationExpression => new(this, "");
[ExportToolButton("")]
public Callable MyButtonOkWithCallableFromExpression => Callable.From(null);
}

View file

@ -0,0 +1,12 @@
using Godot;
using System;
[Tool]
public partial class ExportedToolButtons : GodotObject
{
[ExportToolButton("Click me!")]
public Callable MyButton1 => Callable.From(() => { GD.Print("Clicked MyButton1!"); });
[ExportToolButton("Click me!", Icon = "ColorRect")]
public Callable MyButton2 => Callable.From(() => { GD.Print("Clicked MyButton2!"); });
}

View file

@ -395,6 +395,11 @@ public class MustBeVariantAnnotatedMethods
public void MethodWithWrongAttribute()
{
}
[NestedGenericTypeAttributeContainer.NestedGenericTypeAttribute<bool>()]
public void MethodWithNestedAttribute()
{
}
}
[GenericTypeAttribute<bool>()]
@ -657,3 +662,11 @@ public class ClassNonVariantAnnotated
public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
{
}
public class NestedGenericTypeAttributeContainer
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class NestedGenericTypeAttribute<[MustBeVariant] T> : Attribute
{
}
}

View file

@ -3,3 +3,7 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0003.html)
GD0108 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0108.html)
GD0109 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0109.html)
GD0110 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0110.html)
GD0111 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0111.html)

View file

@ -107,6 +107,46 @@ namespace Godot.SourceGenerators
"Types not derived from Node should not export Node members. Node export is only supported in Node-derived classes.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0107"));
public static readonly DiagnosticDescriptor OnlyToolClassesShouldUseExportToolButtonRule =
new DiagnosticDescriptor(id: "GD0108",
title: "The exported tool button is not in a tool class",
messageFormat: "The exported tool button '{0}' is not in a tool class",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The exported tool button is not in a tool class. Annotate the class with the '[Tool]' attribute, or remove the '[ExportToolButton]' attribute.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0108"));
public static readonly DiagnosticDescriptor ExportToolButtonShouldNotBeUsedWithExportRule =
new DiagnosticDescriptor(id: "GD0109",
title: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute",
messageFormat: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute on '{0}'",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The '[ExportToolButton]' attribute cannot be used with the '[Export]' attribute. Remove one of the attributes.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0109"));
public static readonly DiagnosticDescriptor ExportToolButtonIsNotCallableRule =
new DiagnosticDescriptor(id: "GD0110",
title: "The exported tool button is not a Callable",
messageFormat: "The exported tool button '{0}' is not a Callable",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The exported tool button is not a Callable. The '[ExportToolButton]' attribute is only supported on members of type Callable.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0110"));
public static readonly DiagnosticDescriptor ExportToolButtonMustBeExpressionBodiedProperty =
new DiagnosticDescriptor(id: "GD0111",
title: "The exported tool button must be an expression-bodied property",
messageFormat: "The exported tool button '{0}' must be an expression-bodied property",
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
"The exported tool button must be an expression-bodied property. The '[ExportToolButton]' attribute is only supported on expression-bodied properties with a 'new Callable(...)' or 'Callable.From(...)' expression.",
helpLinkUri: string.Format(_helpLinkFormat, "GD0111"));
public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule =
new DiagnosticDescriptor(id: "GD0201",
title: "The name of the delegate must end with 'EventHandler'",

View file

@ -155,6 +155,32 @@ namespace Godot.SourceGenerators
};
}
public static string GetAccessibilityKeyword(this INamedTypeSymbol namedTypeSymbol)
{
if (namedTypeSymbol.DeclaredAccessibility == Accessibility.NotApplicable)
{
// Accessibility not specified. Get the default accessibility.
return namedTypeSymbol.ContainingSymbol switch
{
null or INamespaceSymbol => "internal",
ITypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } => "private",
ITypeSymbol { TypeKind: TypeKind.Interface } => "public",
_ => "",
};
}
return namedTypeSymbol.DeclaredAccessibility switch
{
Accessibility.Private => "private",
Accessibility.Protected => "protected",
Accessibility.Internal => "internal",
Accessibility.ProtectedAndInternal => "private",
Accessibility.ProtectedOrInternal => "private",
Accessibility.Public => "public",
_ => "",
};
}
public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
{
return symbol.IsGenericType ?
@ -261,6 +287,12 @@ namespace Godot.SourceGenerators
public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr;
public static bool IsGodotExportToolButtonAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportToolButtonAttr;
public static bool IsGodotToolAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ToolAttr;
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr;

View file

@ -3,13 +3,14 @@
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<PropertyGroup>
<Description>Core C# source generator for Godot projects.</Description>
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.SourceGenerators</PackageId>
<Version>4.3.0</Version>
<Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>

View file

@ -4,15 +4,18 @@ namespace Godot.SourceGenerators
{
public const string GodotObject = "Godot.GodotObject";
public const string Node = "Godot.Node";
public const string Callable = "Godot.Callable";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
public const string ExportAttr = "Godot.ExportAttribute";
public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute";
public const string ExportGroupAttr = "Godot.ExportGroupAttribute";
public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute";
public const string ExportToolButtonAttr = "Godot.ExportToolButtonAttribute";
public const string SignalAttr = "Godot.SignalAttribute";
public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute";
public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute";
public const string GlobalClassAttr = "Godot.GlobalClassAttribute";
public const string ToolAttr = "Godot.ToolAttribute";
public const string SystemFlagsAttr = "System.FlagsAttribute";
}
}

View file

@ -88,7 +88,9 @@ namespace Godot.SourceGenerators
HideQuaternionEdit = 35,
Password = 36,
LayersAvoidance = 37,
Max = 38
DictionaryType = 38,
ToolButton = 39,
Max = 40
}
[Flags]

View file

@ -274,6 +274,14 @@ namespace Godot.SourceGenerators
return null;
}
public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol)
{
if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
return genericType.TypeArguments.ToArray();
return null;
}
private static StringBuilder Append(this StringBuilder source, string a, string b)
=> source.Append(a).Append(b);

View file

@ -135,7 +135,7 @@ namespace Godot.SourceGenerators
{
ITypeParameterSymbol? typeParamSymbol = parentSymbol switch
{
IMethodSymbol methodSymbol when parentSyntax.Parent is AttributeSyntax &&
IMethodSymbol methodSymbol when parentSyntax.Ancestors().Any(s => s is AttributeSyntax) &&
methodSymbol.ContainingType.TypeParameters.Length > 0
=> methodSymbol.ContainingType.TypeParameters[typeArgumentIndex],

View file

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
@ -68,6 +70,7 @@ namespace Godot.SourceGenerators
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
bool isToolClass = symbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotToolAttribute() ?? false);
string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptProperties.generated";
@ -276,6 +279,16 @@ namespace Godot.SourceGenerators
if (propertyInfo == null)
continue;
if (propertyInfo.Value.Hint == PropertyHint.ToolButton && !isToolClass)
{
context.ReportDiagnostic(Diagnostic.Create(
Common.OnlyToolClassesShouldUseExportToolButtonRule,
member.Symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
member.Symbol.ToDisplayString()
));
continue;
}
AppendPropertyInfo(source, propertyInfo.Value);
}
@ -417,31 +430,127 @@ namespace Godot.SourceGenerators
var exportAttr = memberSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false);
var exportToolButtonAttr = memberSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGodotExportToolButtonAttribute() ?? false);
if (exportAttr != null && exportToolButtonAttr != null)
{
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportToolButtonShouldNotBeUsedWithExportRule,
memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
memberSymbol.ToDisplayString()
));
return null;
}
var propertySymbol = memberSymbol as IPropertySymbol;
var fieldSymbol = memberSymbol as IFieldSymbol;
if (exportAttr != null && propertySymbol != null)
{
if (propertySymbol.GetMethod == null)
if (propertySymbol.GetMethod == null || propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
{
// Exports can be neither read-only nor write-only but the diagnostic errors for properties are already
// reported by ScriptPropertyDefValGenerator.cs so just quit early here.
return null;
}
}
if (exportToolButtonAttr != null && propertySymbol != null && propertySymbol.GetMethod == null)
{
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportedPropertyIsWriteOnlyRule,
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
propertySymbol.ToDisplayString()
));
return null;
}
if (exportToolButtonAttr != null && propertySymbol != null)
{
if (!PropertyIsExpressionBodiedAndReturnsNewCallable(context.Compilation, propertySymbol))
{
// This should never happen, as we filtered WriteOnly properties, but just in case.
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportedPropertyIsWriteOnlyRule,
Common.ExportToolButtonMustBeExpressionBodiedProperty,
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
propertySymbol.ToDisplayString()
));
return null;
}
if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
static bool PropertyIsExpressionBodiedAndReturnsNewCallable(Compilation compilation, IPropertySymbol? propertySymbol)
{
// This should never happen, as we filtered ReadOnly properties, but just in case.
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportedMemberIsReadOnlyRule,
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
propertySymbol.ToDisplayString()
));
return null;
if (propertySymbol == null)
{
return false;
}
var propertyDeclarationSyntax = propertySymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault();
if (propertyDeclarationSyntax == null || propertyDeclarationSyntax.Initializer != null)
{
return false;
}
if (propertyDeclarationSyntax.AccessorList != null)
{
var accessors = propertyDeclarationSyntax.AccessorList.Accessors;
foreach (var accessor in accessors)
{
if (!accessor.IsKind(SyntaxKind.GetAccessorDeclaration))
{
// Only getters are allowed.
return false;
}
if (!ExpressionBodyReturnsNewCallable(compilation, accessor.ExpressionBody))
{
return false;
}
}
}
else if (!ExpressionBodyReturnsNewCallable(compilation, propertyDeclarationSyntax.ExpressionBody))
{
return false;
}
return true;
}
static bool ExpressionBodyReturnsNewCallable(Compilation compilation, ArrowExpressionClauseSyntax? expressionSyntax)
{
if (expressionSyntax == null)
{
return false;
}
var semanticModel = compilation.GetSemanticModel(expressionSyntax.SyntaxTree);
switch (expressionSyntax.Expression)
{
case ImplicitObjectCreationExpressionSyntax creationExpression:
// We already validate that the property type must be 'Callable'
// so we can assume this constructor is valid.
return true;
case ObjectCreationExpressionSyntax creationExpression:
var typeSymbol = semanticModel.GetSymbolInfo(creationExpression.Type).Symbol as ITypeSymbol;
if (typeSymbol != null)
{
return typeSymbol.FullQualifiedNameOmitGlobal() == GodotClasses.Callable;
}
break;
case InvocationExpressionSyntax invocationExpression:
var methodSymbol = semanticModel.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol;
if (methodSymbol != null && methodSymbol.Name == "From")
{
return methodSymbol.ContainingType.FullQualifiedNameOmitGlobal() == GodotClasses.Callable;
}
break;
}
return false;
}
}
@ -450,14 +559,41 @@ namespace Godot.SourceGenerators
var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
string memberName = memberSymbol.Name;
string? hintString = null;
if (exportToolButtonAttr != null)
{
if (memberVariantType != VariantType.Callable)
{
context.ReportDiagnostic(Diagnostic.Create(
Common.ExportToolButtonIsNotCallableRule,
memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
memberSymbol.ToDisplayString()
));
return null;
}
hintString = exportToolButtonAttr.ConstructorArguments[0].Value?.ToString() ?? "";
foreach (var namedArgument in exportToolButtonAttr.NamedArguments)
{
if (namedArgument is { Key: "Icon", Value.Value: string { Length: > 0 } })
{
hintString += $",{namedArgument.Value.Value}";
}
}
return new PropertyInfo(memberVariantType, memberName, PropertyHint.ToolButton,
hintString: hintString, PropertyUsageFlags.Editor, exported: true);
}
if (exportAttr == null)
{
return new PropertyInfo(memberVariantType, memberName, PropertyHint.None,
hintString: null, PropertyUsageFlags.ScriptVariable, exported: false);
hintString: hintString, PropertyUsageFlags.ScriptVariable, exported: false);
}
if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType,
isTypeArgument: false, out var hint, out var hintString))
isTypeArgument: false, out var hint, out hintString))
{
var constructorArguments = exportAttr.ConstructorArguments;
@ -728,8 +864,81 @@ namespace Godot.SourceGenerators
if (!isTypeArgument && variantType == VariantType.Dictionary)
{
// TODO: Dictionaries are not supported in the inspector
return false;
var elementTypes = MarshalUtils.GetGenericElementTypes(type);
if (elementTypes == null)
return false; // Non-generic Dictionary, so there's no hint to add
Debug.Assert(elementTypes.Length == 2);
var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache);
var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache);
if (keyElementMarshalType == null || valueElementMarshalType == null)
{
// To maintain compatibility with previous versions of Godot before 4.4,
// we must preserve the old behavior for generic dictionaries with non-marshallable
// generic type arguments.
return false;
}
var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType.Value)!.Value;
var keyIsPresetHint = false;
var keyHintString = (string?)null;
if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName)
keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString);
if (!keyIsPresetHint)
{
bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0],
exportAttr, keyElementVariantType, isTypeArgument: true,
out var keyElementHint, out var keyElementHintString);
// Format: type/hint:hint_string
if (hintRes)
{
keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":";
if (keyElementHintString != null)
keyHintString += keyElementHintString;
}
else
{
keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":";
}
}
var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType.Value)!.Value;
var valueIsPresetHint = false;
var valueHintString = (string?)null;
if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName)
valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString);
if (!valueIsPresetHint)
{
bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1],
exportAttr, valueElementVariantType, isTypeArgument: true,
out var valueElementHint, out var valueElementHintString);
// Format: type/hint:hint_string
if (hintRes)
{
valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":";
if (valueElementHintString != null)
valueHintString += valueElementHintString;
}
else
{
valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":";
}
}
hint = PropertyHint.TypeString;
hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
return hintString != null;
}
return false;

View file

@ -196,16 +196,13 @@ namespace Godot.SourceGenerators
continue;
}
if (marshalType == MarshalType.GodotObjectOrDerived)
if (!isNode && MemberHasNodeType(propertyType, marshalType.Value))
{
if (!isNode && propertyType.InheritsFrom("GodotSharp", GodotClasses.Node))
{
context.ReportDiagnostic(Diagnostic.Create(
Common.OnlyNodesShouldExportNodesRule,
property.Locations.FirstLocationWithSourceTreeOrDefault()
));
continue;
}
context.ReportDiagnostic(Diagnostic.Create(
Common.OnlyNodesShouldExportNodesRule,
property.Locations.FirstLocationWithSourceTreeOrDefault()
));
continue;
}
var propertyDeclarationSyntax = property.DeclaringSyntaxReferences
@ -315,16 +312,13 @@ namespace Godot.SourceGenerators
continue;
}
if (marshalType == MarshalType.GodotObjectOrDerived)
if (!isNode && MemberHasNodeType(fieldType, marshalType.Value))
{
if (!isNode && fieldType.InheritsFrom("GodotSharp", GodotClasses.Node))
{
context.ReportDiagnostic(Diagnostic.Create(
Common.OnlyNodesShouldExportNodesRule,
field.Locations.FirstLocationWithSourceTreeOrDefault()
));
continue;
}
context.ReportDiagnostic(Diagnostic.Create(
Common.OnlyNodesShouldExportNodesRule,
field.Locations.FirstLocationWithSourceTreeOrDefault()
));
continue;
}
EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences
@ -424,6 +418,27 @@ namespace Godot.SourceGenerators
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static bool MemberHasNodeType(ITypeSymbol memberType, MarshalType marshalType)
{
if (marshalType == MarshalType.GodotObjectOrDerived)
{
return memberType.InheritsFrom("GodotSharp", GodotClasses.Node);
}
if (marshalType == MarshalType.GodotObjectOrDerivedArray)
{
var elementType = ((IArrayTypeSymbol)memberType).ElementType;
return elementType.InheritsFrom("GodotSharp", GodotClasses.Node);
}
if (memberType is INamedTypeSymbol { IsGenericType: true } genericType)
{
return genericType.TypeArguments
.Any(static typeArgument
=> typeArgument.InheritsFrom("GodotSharp", GodotClasses.Node));
}
return false;
}
private struct ExportedPropertyMetadata
{
public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value)

View file

@ -5,13 +5,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
// TODO:
// Determine a proper way to emit the signal.
// 'Emit(nameof(TheEvent))' creates a StringName every time and has the overhead of string marshaling.
// I haven't decided on the best option yet. Some possibilities:
// - Expose the generated StringName fields to the user, for use with 'Emit(...)'.
// - Generate a 'EmitSignalName' method for each event signal.
namespace Godot.SourceGenerators
{
[Generator]
@ -276,7 +269,7 @@ namespace Godot.SourceGenerators
source.Append(
$" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n");
source.Append(" public event ")
source.Append($" {signalDelegate.DelegateSymbol.GetAccessibilityKeyword()} event ")
.Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal())
.Append(" @")
.Append(signalName)
@ -288,6 +281,43 @@ namespace Godot.SourceGenerators
.Append(signalName)
.Append(" -= value;\n")
.Append("}\n");
// Generate EmitSignal{EventName} method to raise the event
var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
int paramCount = invokeMethodSymbol.Parameters.Length;
string raiseMethodModifiers = signalDelegate.DelegateSymbol.ContainingType.IsSealed ?
"private" :
"protected";
source.Append($" {raiseMethodModifiers} void EmitSignal{signalName}(");
for (int i = 0; i < paramCount; i++)
{
var paramSymbol = invokeMethodSymbol.Parameters[i];
source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} @{paramSymbol.Name}");
if (i < paramCount - 1)
{
source.Append(", ");
}
}
source.Append(")\n");
source.Append(" {\n");
source.Append($" EmitSignal(SignalName.{signalName}");
foreach (var paramSymbol in invokeMethodSymbol.Parameters)
{
// Enums must be converted to the underlying type before they can be implicitly converted to Variant
if (paramSymbol.Type.TypeKind == TypeKind.Enum)
{
var underlyingType = ((INamedTypeSymbol)paramSymbol.Type).EnumUnderlyingType!;
source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()})@{paramSymbol.Name}");
continue;
}
source.Append($", @{paramSymbol.Name}");
}
source.Append(");\n");
source.Append(" }\n");
}
// Generate RaiseGodotClassSignalCallbacks

View file

@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(errorLine);
@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(warningLine);

View file

@ -1,8 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -5,7 +5,6 @@
<TargetFramework>net6.0-windows</TargetFramework>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>False</SelfContained>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>

View file

@ -1,17 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="NuGet.Frameworks" Version="6.12.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
<ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -12,6 +12,8 @@ namespace GodotTools.ProjectEditor
{
public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}";
public static string GodotMinimumRequiredTfm => "net8.0";
public static ProjectRootElement GenGameProject(string name)
{
if (name.Length == 0)
@ -22,13 +24,7 @@ namespace GodotTools.ProjectEditor
root.Sdk = GodotSdkAttrValue;
var mainGroup = root.AddPropertyGroup();
mainGroup.AddProperty("TargetFramework", "net6.0");
var net7 = mainGroup.AddProperty("TargetFramework", "net7.0");
net7.Condition = " '$(GodotTargetPlatform)' == 'android' ";
var net8 = mainGroup.AddProperty("TargetFramework", "net8.0");
net8.Condition = " '$(GodotTargetPlatform)' == 'ios' ";
mainGroup.AddProperty("TargetFramework", GodotMinimumRequiredTfm);
mainGroup.AddProperty("EnableDynamicLoading", "true");

View file

@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
using NuGet.Frameworks;
namespace GodotTools.ProjectEditor
{
@ -19,8 +23,21 @@ namespace GodotTools.ProjectEditor
}
}
public static class ProjectUtils
public static partial class ProjectUtils
{
[GeneratedRegex(@"\s*'\$\(GodotTargetPlatform\)'\s*==\s*'(?<platform>[A-z]+)'\s*", RegexOptions.IgnoreCase)]
private static partial Regex GodotTargetPlatformConditionRegex();
private static readonly string[] _platformNames =
{
"windows",
"linuxbsd",
"macos",
"android",
"ios",
"web",
};
public static void MSBuildLocatorRegisterLatest(out Version version, out string path)
{
var instance = MSBuildLocator.QueryVisualStudioInstances()
@ -36,11 +53,22 @@ namespace GodotTools.ProjectEditor
public static MSBuildProject? Open(string path)
{
var root = ProjectRootElement.Open(path);
var root = ProjectRootElement.Open(path, ProjectCollection.GlobalProjectCollection, preserveFormatting: true);
return root != null ? new MSBuildProject(root) : null;
}
public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
public static void UpgradeProjectIfNeeded(MSBuildProject project, string projectName)
{
// NOTE: The order in which changes are made to the project is important.
// Migrate to MSBuild project Sdks style if using the old style.
MigrateToProjectSdksStyle(project, projectName);
EnsureGodotSdkIsUpToDate(project);
EnsureTargetFrameworkMatchesMinimumRequirement(project);
}
private static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
var origRoot = project.Root;
@ -64,5 +92,128 @@ namespace GodotTools.ProjectEditor
root.Sdk = godotSdkAttrValue;
project.HasUnsavedChanges = true;
}
private static void EnsureTargetFrameworkMatchesMinimumRequirement(MSBuildProject project)
{
var root = project.Root;
string minTfmValue = ProjectGenerator.GodotMinimumRequiredTfm;
var minTfmVersion = NuGetFramework.Parse(minTfmValue).Version;
ProjectPropertyGroupElement? mainPropertyGroup = null;
ProjectPropertyElement? mainTargetFrameworkProperty = null;
var propertiesToChange = new List<ProjectPropertyElement>();
foreach (var propertyGroup in root.PropertyGroups)
{
bool groupHasCondition = !string.IsNullOrEmpty(propertyGroup.Condition);
// Check if the property group should be excluded from checking for 'TargetFramework' properties.
if (groupHasCondition && !ConditionMatchesGodotPlatform(propertyGroup.Condition))
{
continue;
}
// Store a reference to the first property group without conditions,
// in case we need to add a new 'TargetFramework' property later.
if (mainPropertyGroup == null && !groupHasCondition)
{
mainPropertyGroup = propertyGroup;
}
foreach (var property in propertyGroup.Properties)
{
// We are looking for 'TargetFramework' properties.
if (property.Name != "TargetFramework")
{
continue;
}
bool propertyHasCondition = !string.IsNullOrEmpty(property.Condition);
// Check if the property should be excluded.
if (propertyHasCondition && !ConditionMatchesGodotPlatform(property.Condition))
{
continue;
}
if (!groupHasCondition && !propertyHasCondition)
{
// Store a reference to the 'TargetFramework' that has no conditions
// because it applies to all platforms.
if (mainTargetFrameworkProperty == null)
{
mainTargetFrameworkProperty = property;
}
continue;
}
// If the 'TargetFramework' property is conditional, it may no longer be needed
// when the main one is upgraded to the new minimum version.
var tfmVersion = NuGetFramework.Parse(property.Value).Version;
if (tfmVersion <= minTfmVersion)
{
propertiesToChange.Add(property);
}
}
}
if (mainTargetFrameworkProperty == null)
{
// We haven't found a 'TargetFramework' property without conditions,
// we'll just add one in the first property group without conditions.
if (mainPropertyGroup == null)
{
// We also don't have a property group without conditions,
// so we'll add a new one to the project.
mainPropertyGroup = root.AddPropertyGroup();
}
mainTargetFrameworkProperty = mainPropertyGroup.AddProperty("TargetFramework", minTfmValue);
project.HasUnsavedChanges = true;
}
else
{
var tfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
if (tfmVersion < minTfmVersion)
{
mainTargetFrameworkProperty.Value = minTfmValue;
project.HasUnsavedChanges = true;
}
}
var mainTfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
foreach (var property in propertiesToChange)
{
// If the main 'TargetFramework' property targets a version newer than
// the minimum required by Godot, we don't want to remove the conditional
// 'TargetFramework' properties, only upgrade them to the new minimum.
// Otherwise, it can be removed.
if (mainTfmVersion > minTfmVersion)
{
property.Value = minTfmValue;
}
else
{
property.Parent.RemoveChild(property);
}
project.HasUnsavedChanges = true;
}
static bool ConditionMatchesGodotPlatform(string condition)
{
// Check if the condition is checking the 'GodotTargetPlatform' for one of the
// Godot platforms with built-in support in the Godot.NET.Sdk.
var match = GodotTargetPlatformConditionRegex().Match(condition);
if (match.Success)
{
string platform = match.Groups["platform"].Value;
return _platformNames.Contains(platform, StringComparer.OrdinalIgnoreCase);
}
return false;
}
}
}
}

View file

@ -1,8 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<!-- Specify compile items manually to avoid including dangling generated items. -->
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<Import Project="GenerateGodotNupkgsVersions.targets" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<!-- Specify compile items manually to avoid including dangling generated items. -->
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<Import Project="GenerateGodotNupkgsVersions.targets" />
</Project>

View file

@ -265,11 +265,6 @@ namespace GodotTools.Build
success = Publish(buildInfo);
}
if (!success)
{
ShowBuildErrorDialog("Failed to publish .NET project");
}
return success;
}

View file

@ -75,7 +75,19 @@ namespace GodotTools.Export
};
}
private string? _maybeLastExportError;
private void AddExceptionMessage(EditorExportPlatform platform, Exception exception)
{
string? exceptionMessage = exception.Message;
if (string.IsNullOrEmpty(exceptionMessage))
{
exceptionMessage = $"Exception thrown: {exception.GetType().Name}";
}
platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage);
// We also print exceptions as we receive them to stderr.
Console.Error.WriteLine(exception);
}
// With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
@ -92,8 +104,8 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
{
_maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
"A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.";
GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
"A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.");
throw new InvalidOperationException($"{path} is a C# file but no solution file exists.");
}
@ -124,16 +136,7 @@ namespace GodotTools.Export
}
catch (Exception e)
{
_maybeLastExportError = e.Message;
// 'maybeLastExportError' cannot be null or empty if there was an error, so we
// must consider the possibility of exceptions being thrown without a message.
if (string.IsNullOrEmpty(_maybeLastExportError))
_maybeLastExportError = $"Exception thrown: {e.GetType().Name}";
GD.PushError($"Failed to export project: {_maybeLastExportError}");
Console.Error.WriteLine(e);
// TODO: Do something on error once _ExportBegin supports failing.
AddExceptionMessage(GetExportPlatform(), e);
}
}
@ -144,7 +147,9 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
return;
if (!DeterminePlatformFromFeatures(features, out string? platform))
string osName = GetExportPlatform().GetOsName();
if (!TryDeterminePlatformFromOSName(osName, out string? platform))
throw new NotSupportedException("Target platform not supported.");
if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
@ -211,6 +216,8 @@ namespace GodotTools.Export
bool embedBuildResults = ((bool)GetOption("dotnet/embed_build_outputs") || platform == OS.Platforms.Android) && platform != OS.Platforms.MacOS;
var exportedJars = new HashSet<string>();
foreach (PublishConfig config in targets)
{
string ridOS = config.RidOS;
@ -240,7 +247,6 @@ namespace GodotTools.Export
{
publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
$"{buildConfig}-{runtimeIdentifier}");
}
outputPaths.Add(publishOutputDir);
@ -317,6 +323,41 @@ namespace GodotTools.Export
{
if (embedBuildResults)
{
if (platform == OS.Platforms.Android)
{
string fileName = Path.GetFileName(path);
if (IsSharedObject(fileName))
{
AddSharedObject(path, tags: new string[] { arch },
Path.Join(projectDataDirName,
Path.GetRelativePath(publishOutputDir,
Path.GetDirectoryName(path)!)));
return;
}
bool IsSharedObject(string fileName)
{
if (fileName.EndsWith(".jar"))
{
// Don't export the same jar twice. Otherwise we will have conflicts.
// This can happen when exporting for multiple architectures. Dotnet
// stores the jars in .godot/mono/temp/bin/Export[Debug|Release] per
// target architecture. Jars are cpu agnostic so only 1 is needed.
var jarName = Path.GetFileName(fileName);
return exportedJars.Add(jarName);
}
if (fileName.EndsWith(".so") || fileName.EndsWith(".a") || fileName.EndsWith(".dex"))
{
return true;
}
return false;
}
}
string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path));
byte[] fileData = File.ReadAllBytes(path);
string hash = Convert.ToBase64String(SHA512.HashData(fileData));
@ -355,24 +396,23 @@ namespace GodotTools.Export
if (outputPaths.Count > 2)
{
// lipo the simulator binaries together
// TODO: Move this to the native lipo implementation we have in the macos export plugin.
var lipoArgs = new List<string>();
lipoArgs.Add("-create");
lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")));
lipoArgs.Add("-output");
lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"));
int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
if (lipoExitCode != 0)
throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
string outputPath = Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib");
string[] files = outputPaths
.Skip(1)
.Select(path => Path.Combine(path, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))
.ToArray();
if (!Internal.LipOCreateFile(outputPath, files))
{
throw new InvalidOperationException($"Failed to 'lipo' simulator binaries.");
}
outputPaths.RemoveRange(2, outputPaths.Count - 2);
}
var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
$"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
string xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, xcFrameworkPath))
{
throw new InvalidOperationException("Failed to generate xcframework.");
}
@ -446,25 +486,22 @@ namespace GodotTools.Export
Directory.Delete(folder, recursive: true);
}
_tempFolders.Clear();
// TODO: The following is just a workaround until the export plugins can be made to abort with errors
// We check for empty as well, because it's set to empty after hot-reloading
if (!string.IsNullOrEmpty(_maybeLastExportError))
{
string lastExportError = _maybeLastExportError;
_maybeLastExportError = null;
GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project");
}
}
private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform)
/// <summary>
/// Tries to determine the platform from the export preset's platform OS name.
/// </summary>
/// <param name="osName">Name of the export operating system.</param>
/// <param name="platform">Platform name for the recognized supported platform.</param>
/// <returns>
/// <see langword="true"/> when the platform OS name is recognized as a supported platform,
/// <see langword="false"/> otherwise.
/// </returns>
private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform)
{
foreach (var feature in features)
if (OS.PlatformFeatureMap.TryGetValue(osName, out platform))
{
if (OS.PlatformFeatureMap.TryGetValue(feature, out platform))
return true;
return true;
}
platform = null;

View file

@ -1,93 +0,0 @@
using System;
using System.IO;
namespace GodotTools.Export
{
public static class XcodeHelper
{
private static string? _XcodePath = null;
public static string XcodePath
{
get
{
if (_XcodePath == null)
{
_XcodePath = FindXcode();
if (_XcodePath == null)
throw new FileNotFoundException("Could not find Xcode.");
}
return _XcodePath;
}
}
private static string? FindSelectedXcode()
{
var outputWrapper = new Godot.Collections.Array();
int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper);
if (exitCode == 0)
{
string output = (string)outputWrapper[0];
return output.Trim();
}
Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}");
return null;
}
public static string? FindXcode()
{
string? selectedXcode = FindSelectedXcode();
if (selectedXcode != null)
{
if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
return selectedXcode;
// The path already pointed to Contents/Developer
var dirInfo = new DirectoryInfo(selectedXcode);
if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" })
{
Console.WriteLine(Path.GetDirectoryName(selectedXcode));
Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name);
Console.Error.WriteLine("Unrecognized path for selected Xcode");
}
else
{
return System.IO.Path.GetFullPath($"{selectedXcode}/../..");
}
}
else
{
Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path");
}
const string XcodeHintPath = "/Applications/Xcode.app";
if (Directory.Exists(XcodeHintPath))
{
if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer")))
return XcodeHintPath;
Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory");
}
return null;
}
public static string FindXcodeTool(string toolName)
{
string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain");
string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName);
if (File.Exists(path))
return path;
throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}");
}
}
}

View file

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using GodotTools.Build;
using GodotTools.Ides;
using GodotTools.Ides.Rider;
@ -176,7 +177,7 @@ namespace GodotTools
private static readonly string[] VsCodeNames =
{
"code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
"code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss", "codium"
};
[UsedImplicitly]
@ -259,11 +260,12 @@ namespace GodotTools
var args = new List<string>
{
Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.dll"),
GodotSharpDirs.ProjectSlnPath,
line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath
};
string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe");
string command = DotNetFinder.FindDotNetExe() ?? "dotnet";
try
{
@ -328,7 +330,7 @@ namespace GodotTools
args.Add("-b");
args.Add(vscodeBundleId);
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// The reusing of existing windows made by the 'open' command might not choose a window that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.Add("-n");
@ -337,6 +339,28 @@ namespace GodotTools
args.Add("--args");
}
// Try VSCodium as a fallback if Visual Studio Code can't be found.
if (!macOSAppBundleInstalled)
{
const string VscodiumBundleId = "com.vscodium.codium";
macOSAppBundleInstalled = Internal.IsMacOSAppBundleInstalled(VscodiumBundleId);
if (macOSAppBundleInstalled)
{
args.Add("-b");
args.Add(VscodiumBundleId);
// The reusing of existing windows made by the 'open' command might not choose a window that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.Add("-n");
// The open process must wait until the application finishes (which is instant in VSCode's case)
args.Add("--wait-apps");
args.Add("--args");
}
}
}
args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!);
@ -359,7 +383,7 @@ namespace GodotTools
{
if (!macOSAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium");
return Error.FileNotFound;
}
@ -369,7 +393,7 @@ namespace GodotTools
{
if (string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium");
return Error.FileNotFound;
}
@ -382,7 +406,7 @@ namespace GodotTools
}
catch (Exception e)
{
GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'");
GD.PushError($"Error when trying to run code editor: Visual Studio Code or VSCodium. Exception message: '{e.Message}'");
}
break;
@ -415,12 +439,7 @@ namespace GodotTools
var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
?? throw new InvalidOperationException("Cannot open C# project.");
// NOTE: The order in which changes are made to the project is important
// Migrate to MSBuild project Sdks style if using the old style
ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, GodotSharpDirs.ProjectAssemblyName);
ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
ProjectUtils.UpgradeProjectIfNeeded(msbuildProject, GodotSharpDirs.ProjectAssemblyName);
if (msbuildProject.HasUnsavedChanges)
{
@ -548,7 +567,7 @@ namespace GodotTools
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
@ -556,14 +575,14 @@ namespace GodotTools
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
@ -700,6 +719,23 @@ namespace GodotTools
private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
var populateConstructorMethod =
AppDomain.CurrentDomain
.GetAssemblies()
.First(x => x.GetName().Name == "GodotSharpEditor")
.GetType("Godot.EditorConstructors")?
.GetMethod("AddEditorConstructors",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (populateConstructorMethod == null)
{
throw new MissingMethodException("Godot.EditorConstructors",
"AddEditorConstructors");
}
populateConstructorMethod.Invoke(null, null);
return new GodotSharpEditor().NativeInstance;
}
}

View file

@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<EnableDynamicLoading>true</EnableDynamicLoading>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<!-- The Godot editor uses the Debug Godot API assemblies -->
<GodotApiConfiguration>Debug</GodotApiConfiguration>
@ -13,13 +14,16 @@
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!-- Needed for our source generators to work despite this not being a Godot game project -->
<PropertyGroup>
<IsGodotToolsProject>true</IsGodotToolsProject>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="IsGodotToolsProject" />
</ItemGroup>
<PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') ">
<!-- The project is part of the Godot source tree -->
<!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' -->
@ -27,6 +31,7 @@
<!-- Must not append '$(TargetFramework)' to the output path in this case -->
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.9" />
@ -41,14 +46,17 @@
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" />
<ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" />
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -35,6 +35,13 @@ namespace GodotTools.Internals
return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn);
}
public static bool LipOCreateFile(string outputPath, string[] files)
{
using godot_string outputPathIn = Marshaling.ConvertStringToNative(outputPath);
using godot_packed_string_array filesIn = Marshaling.ConvertSystemArrayToNativePackedStringArray(files);
return godot_icall_Internal_LipOCreateFile(outputPathIn, filesIn);
}
public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble();
@ -121,6 +128,8 @@ namespace GodotTools.Internals
private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId);
private static partial bool godot_icall_Internal_LipOCreateFile(in godot_string outputPath, in godot_packed_string_array files);
private static partial bool godot_icall_Internal_GodotIs32Bits();
private static partial bool godot_icall_Internal_GodotIsRealTDouble();

File diff suppressed because it is too large Load diff

View file

@ -265,6 +265,7 @@ class BindingsGenerator {
bool is_singleton = false;
bool is_singleton_instance = false;
bool is_ref_counted = false;
bool is_span_compatible = false;
/**
* Class is a singleton, but can't be declared as a static class as that would
@ -803,15 +804,17 @@ class BindingsGenerator {
void _append_text_param(StringBuilder &p_output, const String &p_link_target);
void _append_text_undeclared(StringBuilder &p_output, const String &p_link_target);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_signal(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype);
void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype);
void _append_xml_signal(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype);
void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype);
void _append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts);
void _append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target);
void _append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal);
void _append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target);
bool _validate_api_type(const TypeInterface *p_target_itype, const TypeInterface *p_source_itype);
int _determine_enum_prefix(const EnumInterface &p_ienum);
void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length);
@ -840,7 +843,7 @@ class BindingsGenerator {
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span);
Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output);

View file

@ -116,7 +116,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
continue;
}
String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length());
String name = prop.name.substr(prop.name.find_char('/') + 1, prop.name.length());
suggestions.push_back(quoted(name));
}
} break;

View file

@ -41,9 +41,11 @@
#include "core/os/os.h"
#include "core/version.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/export/lipo.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
@ -117,6 +119,13 @@ bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle
#endif
}
bool godot_icall_Internal_LipOCreateFile(const godot_string *p_output_path, const godot_packed_array *p_files) {
String output_path = *reinterpret_cast<const String *>(p_output_path);
PackedStringArray files = *reinterpret_cast<const PackedStringArray *>(p_files);
LipO lip;
return lip.create_file(output_path, files);
}
bool godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
@ -157,7 +166,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line,
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
void godot_icall_Internal_EditorRunPlay() {
@ -258,6 +267,7 @@ static const void *unmanaged_callbacks[]{
(void *)godot_icall_EditorProgress_Step,
(void *)godot_icall_Internal_FullExportTemplatesDir,
(void *)godot_icall_Internal_IsMacOSAppBundleInstalled,
(void *)godot_icall_Internal_LipOCreateFile,
(void *)godot_icall_Internal_GodotIs32Bits,
(void *)godot_icall_Internal_GodotIsRealTDouble,
(void *)godot_icall_Internal_GodotMainIteration,

View file

@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) {
#endif
}
#ifndef WINDOWS_ENABLED
bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) {
Error err = OK;
Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err);
@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_
r_dotnet_root = line;
return true;
}
#endif
bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
#if defined(WINDOWS_ENABLED)
@ -260,7 +262,7 @@ bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
return false;
}
r_dotnet_root = String::utf16((const char16_t *)buffer.ptr());
r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()).replace("\\", "/");
RegCloseKey(hkey);
return true;
#else

View file

@ -1,16 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
import editor.template_builders as build_template_cs
env["BUILDERS"]["MakeCSharpTemplateBuilder"] = Builder(
action=env.Run(build_template_cs.make_templates),
suffix=".h",
src_suffix=".cs",
)
# Template files
templates_sources = Glob("*/*.cs")
env.Alias("editor_template_cs", [env.MakeCSharpTemplateBuilder("templates.gen.h", templates_sources)])
env.CommandNoCache("templates.gen.h", Glob("*/*.cs"), env.Run(build_template_cs.make_templates))

View file

@ -3,6 +3,8 @@
using _BINDINGS_NAMESPACE_;
using System;
[Tool]
[GlobalClass]
public partial class VisualShaderNode_CLASS_ : _BASE_
{
public override string _GetName()
@ -20,37 +22,37 @@ public partial class VisualShaderNode_CLASS_ : _BASE_
return "";
}
public override long _GetReturnIconType()
public override VisualShaderNode.PortType _GetReturnIconType()
{
return 0;
}
public override long _GetInputPortCount()
public override int _GetInputPortCount()
{
return 0;
}
public override string _GetInputPortName(long port)
public override string _GetInputPortName(int port)
{
return "";
}
public override long _GetInputPortType(long port)
public override VisualShaderNode.PortType _GetInputPortType(int port)
{
return 0;
}
public override long _GetOutputPortCount()
public override int _GetOutputPortCount()
{
return 1;
}
public override string _GetOutputPortName(long port)
public override string _GetOutputPortName(int port)
{
return "result";
}
public override long _GetOutputPortType(long port)
public override VisualShaderNode.PortType _GetOutputPortType(int port)
{
return 0;
}

View file

@ -32,6 +32,7 @@
#define SEMVER_H
#include "core/string/ustring.h"
#include "modules/regex/regex.h"
// <sys/sysmacros.h> is included somewhere, which defines major(dev) to gnu_dev_major(dev)